Compare commits

...

8 commits

Author SHA1 Message Date
DHR60
e14a85c144
Merge f7409053cd into b5800f7dfc 2026-02-15 09:01:35 +00:00
DHR60
f7409053cd Node test with sub chain 2026-02-15 17:01:09 +08:00
DHR60
a03e38fb16 Fix 2026-02-15 16:48:44 +08:00
DHR60
84a1eb8445 Optimize ProfileItem acquisition speed 2026-02-15 16:48:44 +08:00
DHR60
f900ce7f3f Fix 2026-02-15 16:48:44 +08:00
DHR60
a89471da92 Support sing-box 1.11 DNS 2026-02-07 21:17:29 +08:00
DHR60
b53101ce58 Update tag naming 2026-02-07 21:17:29 +08:00
DHR60
c3223e1fc4 Refactor core config gen 2026-02-07 21:17:29 +08:00
29 changed files with 1279 additions and 2198 deletions

View file

@ -15,7 +15,6 @@ public class Global
public const string CoreConfigFileName = "config.json"; public const string CoreConfigFileName = "config.json";
public const string CorePreConfigFileName = "configPre.json"; public const string CorePreConfigFileName = "configPre.json";
public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
public const string ClashMixinConfigFileName = "Mixin.yaml"; public const string ClashMixinConfigFileName = "Mixin.yaml";
public const string NamespaceSample = "ServiceLib.Sample."; public const string NamespaceSample = "ServiceLib.Sample.";
@ -88,7 +87,6 @@ public class Global
public const string SingboxLocalDNSTag = "local_local"; public const string SingboxLocalDNSTag = "local_local";
public const string SingboxHostsDNSTag = "hosts_dns"; public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns"; public const string SingboxFakeDNSTag = "fake_dns";
public const string SingboxEchDNSTag = "ech_dns";
public const int Hysteria2DefaultHopInt = 10; public const int Hysteria2DefaultHopInt = 10;

View file

@ -1231,26 +1231,16 @@ public static class ConfigHandler
/// <param name="node">Server node that might need pre-SOCKS</param> /// <param name="node">Server node that might need pre-SOCKS</param>
/// <param name="coreType">Core type being used</param> /// <param name="coreType">Core type being used</param>
/// <returns>A SOCKS profile item or null if not needed</returns> /// <returns>A SOCKS profile item or null if not needed</returns>
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
{ {
ProfileItem? itemSocks = null; ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{ {
var tun2SocksAddress = node.Address;
if (node.ConfigType.IsGroupType())
{
var lstAddresses = (await GroupProfileManager.GetAllChildDomainAddresses(node)).ToList();
if (lstAddresses.Count > 0)
{
tun2SocksAddress = Utils.List2String(lstAddresses);
}
}
itemSocks = new ProfileItem() itemSocks = new ProfileItem()
{ {
CoreType = ECoreType.sing_box, CoreType = ECoreType.sing_box,
ConfigType = EConfigType.SOCKS, ConfigType = EConfigType.SOCKS,
Address = Global.Loopback, Address = Global.Loopback,
SpiderX = tun2SocksAddress, // Tun2SocksAddress
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks) Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
}; };
} }
@ -1265,7 +1255,6 @@ public static class ConfigHandler
Port = node.PreSocksPort.Value, Port = node.PreSocksPort.Value,
}; };
} }
await Task.CompletedTask;
return itemSocks; return itemSocks;
} }

View file

@ -7,27 +7,27 @@ public static class CoreConfigHandler
{ {
private static readonly string _tag = "CoreConfigHandler"; private static readonly string _tag = "CoreConfigHandler";
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName) public static async Task<RetResult> GenerateClientConfig(CoreConfigContext context, string? fileName)
{ {
var config = AppManager.Instance.Config; var config = AppManager.Instance.Config;
var result = new RetResult(); var result = new RetResult();
var node = context.Node;
if (node.ConfigType == EConfigType.Custom) if (node.ConfigType == EConfigType.Custom)
{ {
result = node.CoreType switch result = node.CoreType switch
{ {
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
_ => await GenerateClientCustomConfig(node, fileName) _ => await GenerateClientCustomConfig(node, fileName)
}; };
} }
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{ {
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
} }
else else
{ {
result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
} }
if (result.Success != true) if (result.Success != true)
{ {
@ -93,13 +93,26 @@ public static class CoreConfigHandler
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType) public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
{ {
var result = new RetResult(); var result = new RetResult();
var context = await BuildCoreConfigContext(config, new());
var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty())
.Select(serverTestItem => serverTestItem.IndexId);
var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids);
foreach (var node in nodes)
{
var actNode = await FillNodeContext(context, node, true);
if (node.IndexId == actNode.IndexId)
{
continue;
}
context.ServerTestItemMap[node.IndexId] = actNode.IndexId;
}
if (coreType == ECoreType.sing_box) if (coreType == ECoreType.sing_box)
{ {
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds);
} }
else if (coreType == ECoreType.Xray) else if (coreType == ECoreType.Xray)
{ {
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds);
} }
if (result.Success != true) if (result.Success != true)
{ {
@ -109,20 +122,21 @@ public static class CoreConfigHandler
return result; return result;
} }
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName)
{ {
var result = new RetResult(); var result = new RetResult();
var node = context.Node;
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum); var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port; testItem.Port = port;
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{ {
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port);
} }
else else
{ {
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port);
} }
if (result.Success != true) if (result.Success != true)
{ {
@ -132,4 +146,127 @@ public static class CoreConfigHandler
await File.WriteAllTextAsync(fileName, result.Data.ToString()); await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result; return result;
} }
public static async Task<CoreConfigContext> BuildCoreConfigContext(Config config, ProfileItem node)
{
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
var context = new CoreConfigContext()
{
Node = node,
AllProxiesMap = [],
AppConfig = config,
FullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(coreType),
IsTunEnabled = config.TunModeItem.EnableTun,
SimpleDnsItem = config.SimpleDNSItem,
ProtectDomainList = [],
ProtectSocksPort = 0,
RawDnsItem = await AppManager.Instance.GetDNSItem(coreType),
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
};
context = context with
{
Node = await FillNodeContext(context, node)
};
if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true))
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(context.RoutingItem?.RuleSet);
foreach (var ruleItem in rules.Where(ruleItem => !Global.OutboundTags.Contains(ruleItem.OutboundTag)))
{
var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag);
if (ruleOutboundNode != null)
{
var ruleOutboundNodeAct = await FillNodeContext(context, ruleOutboundNode, false);
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = ruleOutboundNodeAct;
}
}
}
return context;
}
private static async Task<ProfileItem> FillNodeContext(CoreConfigContext context, ProfileItem node, bool includeSubChain = true)
{
if (node.IndexId.IsNullOrEmpty())
{
return node;
}
var newItems = new List<ProfileItem> { node };
if (node.ConfigType.IsGroupType())
{
var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node);
foreach (var childItem in groupChildList.Where(childItem => !context.AllProxiesMap.ContainsKey(childItem.IndexId)))
{
await FillNodeContext(context, childItem, false);
}
node.SetProtocolExtra(node.GetProtocolExtra() with
{
ChildItems = Utils.List2String(groupChildList.Select(n => n.IndexId).ToList()),
});
newItems.AddRange(groupChildList);
}
context.AllProxiesMap[node.IndexId] = node;
foreach (var item in newItems)
{
var address = item.Address;
if (Utils.IsDomain(address))
{
context.ProtectDomainList.Add(address);
}
if (item.EchConfigList.IsNullOrEmpty())
{
continue;
}
var echQuerySni = item.Sni;
if (item.StreamSecurity == Global.StreamSecurity
&& item.EchConfigList?.Contains("://") == true)
{
var idx = item.EchConfigList.IndexOf('+');
echQuerySni = idx > 0 ? item.EchConfigList[..idx] : item.Sni;
}
if (!Utils.IsDomain(echQuerySni))
{
continue;
}
context.ProtectDomainList.Add(echQuerySni);
}
if (!includeSubChain || node.Subid.IsNullOrEmpty())
{
return node;
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem == null)
{
return node;
}
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (prevNode is null && nextNode is null)
{
return node;
}
var prevNodeAct = prevNode is null ? null : await FillNodeContext(context, prevNode, false);
var nextNodeAct = nextNode is null ? null : await FillNodeContext(context, nextNode, false);
// Build new proxy chain node
var chainNode = new ProfileItem()
{
IndexId = $"inner-{Utils.GetGuid(false)}",
ConfigType = EConfigType.ProxyChain,
CoreType = node.CoreType ?? ECoreType.Xray,
};
List<string?> childItems = [prevNodeAct?.IndexId, node.IndexId, nextNodeAct?.IndexId];
var chainExtraItem = chainNode.GetProtocolExtra() with
{
GroupType = chainNode.ConfigType.ToString(),
ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())),
};
chainNode.SetProtocolExtra(chainExtraItem);
context.AllProxiesMap[chainNode.IndexId] = chainNode;
return chainNode;
}
} }

View file

@ -230,6 +230,18 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId); return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
} }
public async Task<List<ProfileItem>> GetProfileItemsByIndexIds(IEnumerable<string> indexIds)
{
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
if (ids.Count == 0)
{
return [];
}
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
.Where(it => ids.Contains(it.IndexId))
.ToListAsync();
}
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks) public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
{ {
if (remarks.IsNullOrEmpty()) if (remarks.IsNullOrEmpty())

View file

@ -66,7 +66,9 @@ public class CoreManager
} }
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName); var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
context = context with { IsTunEnabled = _config.TunModeItem.EnableTun };
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true) if (result.Success != true)
{ {
await UpdateFunc(true, result.Msg); await UpdateFunc(true, result.Msg);
@ -85,8 +87,8 @@ public class CoreManager
await WindowsUtils.RemoveTunDevice(); await WindowsUtils.RemoveTunDevice();
} }
await CoreStart(node); await CoreStart(context);
await CoreStartPreService(node); await CoreStartPreService(context);
if (_processService != null) if (_processService != null)
{ {
await UpdateFunc(true, $"{node.GetSummary()}"); await UpdateFunc(true, $"{node.GetSummary()}");
@ -122,7 +124,8 @@ public class CoreManager
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName); var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath);
if (result.Success != true) if (result.Success != true)
{ {
return null; return null;
@ -165,8 +168,9 @@ public class CoreManager
#region Private #region Private
private async Task CoreStart(ProfileItem node) private async Task CoreStart(CoreConfigContext context)
{ {
var node = context.Node;
var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
@ -179,17 +183,20 @@ public class CoreManager
_processService = proc; _processService = proc;
} }
private async Task CoreStartPreService(ProfileItem node) private async Task CoreStartPreService(CoreConfigContext context)
{ {
var node = context.Node;
if (_processService != null && !_processService.HasExited) if (_processService != null && !_processService.HasExited)
{ {
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); var itemSocks = ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null) if (itemSocks != null)
{ {
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); var itemSocksContext = await CoreConfigHandler.BuildCoreConfigContext(_config, itemSocks);
itemSocksContext.ProtectDomainList.AddRangeSafe(context.ProtectDomainList);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocksContext, fileName);
if (result.Success) if (result.Success)
{ {
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);

View file

@ -79,7 +79,7 @@ public class GroupProfileManager
{ {
if (protocolExtra == null) if (protocolExtra == null)
{ {
return new(); return [];
} }
var items = new List<ProfileItem>(); var items = new List<ProfileItem>();
@ -93,27 +93,44 @@ public class GroupProfileManager
{ {
if (extra == null || extra.ChildItems.IsNullOrEmpty()) if (extra == null || extra.ChildItems.IsNullOrEmpty())
{ {
return new(); return [];
} }
var childProfiles = (await Task.WhenAll( var childProfileIds = Utils.String2List(extra.ChildItems)
(Utils.String2List(extra.ChildItems) ?? new()) ?.Where(p => !string.IsNullOrEmpty(p))
.Where(p => !p.IsNullOrEmpty()) .ToList() ?? [];
.Select(AppManager.Instance.GetProfileItem) if (childProfileIds.Count == 0)
)) {
.Where(p => return [];
p != null && }
p.IsValid() &&
p.ConfigType != EConfigType.Custom var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds);
) if (childProfiles == null || childProfiles.Count == 0)
.ToList(); {
return childProfiles ?? new(); return [];
}
var profileMap = childProfiles
.Where(p => p != null && !p.IndexId.IsNullOrEmpty())
.GroupBy(p => p!.IndexId!)
.ToDictionary(g => g.Key, g => g.First());
var ordered = new List<ProfileItem>(childProfileIds.Count);
foreach (var id in childProfileIds)
{
if (id != null && profileMap.TryGetValue(id, out var item) && item != null)
{
ordered.Add(item);
}
}
return ordered;
} }
private static async Task<List<ProfileItem>> GetSubChildProfileItems(ProtocolExtraItem? extra) private static async Task<List<ProfileItem>> GetSubChildProfileItems(ProtocolExtraItem? extra)
{ {
if (extra == null || extra.SubChildItems.IsNullOrEmpty()) if (extra == null || extra.SubChildItems.IsNullOrEmpty())
{ {
return new(); return [];
} }
var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty); var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty);
@ -123,59 +140,30 @@ public class GroupProfileManager
!p.ConfigType.IsComplexType() && !p.ConfigType.IsComplexType() &&
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter)) (extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
) )
.ToList() ?? new(); .ToList() ?? [];
} }
public static async Task<HashSet<string>> GetAllChildDomainAddresses(ProfileItem profileItem) public static async Task<List<ProfileItem>> GetAllChildProfileItems(ProfileItem profileItem)
{ {
var childAddresses = new HashSet<string>(); var allChildItems = new List<ProfileItem>();
var (childItems, _) = await GetChildProfileItems(profileItem); var visited = new HashSet<string>();
foreach (var child in childItems)
{ await CollectChildItems(profileItem, allChildItems, visited);
if (!child.IsComplex())
{ return allChildItems;
childAddresses.Add(child.Address);
}
else if (child.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(child);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
} }
public static async Task<HashSet<string>> GetAllChildEchQuerySni(ProfileItem profileItem) private static async Task CollectChildItems(ProfileItem profileItem, List<ProfileItem> allChildItems, HashSet<string> visited)
{ {
var childAddresses = new HashSet<string>();
var (childItems, _) = await GetChildProfileItems(profileItem); var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var childNode in childItems) foreach (var child in childItems.Where(child => visited.Add(child.IndexId)))
{ {
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty()) allChildItems.Add(child);
if (child.ConfigType.IsGroupType())
{ {
if (childNode.StreamSecurity == Global.StreamSecurity await CollectChildItems(child, allChildItems, visited);
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
} }
} }
} }
return childAddresses;
}
} }

View file

@ -0,0 +1,20 @@
namespace ServiceLib.Models;
public record CoreConfigContext
{
public required ProfileItem Node { get; init; }
public RoutingItem? RoutingItem { get; init; }
public DNSItem? RawDnsItem { get; init; }
public SimpleDNSItem SimpleDnsItem { get; init; } = new();
public Dictionary<string, ProfileItem> AllProxiesMap { get; init; } = new();
public Config AppConfig { get; init; } = new();
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
// Test ServerTestItem Map
public Dictionary<string, string> ServerTestItemMap { get; init; } = new();
// TUN Compatibility
public bool IsTunEnabled { get; init; } = false;
public HashSet<string> ProtectDomainList { get; init; } = new();
public int ProtectSocksPort { get; init; } = 0;
}

View file

@ -255,9 +255,8 @@ public class Server4Sbox : BaseServer4Sbox
// public List<string>? path { get; set; } // hosts // public List<string>? path { get; set; } // hosts
public Dictionary<string, List<string>>? predefined { get; set; } public Dictionary<string, List<string>>? predefined { get; set; }
// Deprecated // Deprecated in sing-box 1.12.0 , kept for backward compatibility
public string? address { get; set; } public string? address { get; set; }
public string? address_resolver { get; set; } public string? address_resolver { get; set; }
public string? address_strategy { get; set; } public string? address_strategy { get; set; }
public string? strategy { get; set; } public string? strategy { get; set; }

View file

@ -1,4 +1,4 @@
{ {
"log": { "log": {
"access": "Vaccess.log", "access": "Vaccess.log",
"error": "Verror.log", "error": "Verror.log",
@ -6,34 +6,6 @@
}, },
"inbounds": [], "inbounds": [],
"outbounds": [ "outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [{
"address": "",
"port": 0,
"users": [{
"id": "",
"security": "auto"
}]
}],
"servers": [{
"address": "",
"method": "",
"ota": false,
"password": "",
"port": 0,
"level": 1
}]
},
"streamSettings": {
"network": "tcp"
},
"mux": {
"enabled": false
}
},
{ {
"protocol": "freedom", "protocol": "freedom",
"tag": "direct" "tag": "direct"

View file

@ -5,19 +5,12 @@
}, },
"inbounds": [], "inbounds": [],
"outbounds": [ "outbounds": [
{
"type": "vless",
"tag": "proxy",
"server": "",
"server_port": 443
},
{ {
"type": "direct", "type": "direct",
"tag": "direct" "tag": "direct"
} }
], ],
"route": { "route": {
"rules": [ "rules": []
]
} }
} }

View file

@ -1,43 +1,34 @@
namespace ServiceLib.Services.CoreConfig; namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService(Config config) public partial class CoreConfigSingboxService(CoreConfigContext context)
{ {
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigSingboxService"; private static readonly string _tag = "CoreConfigSingboxService";
private readonly Config _config = context.AppConfig;
private readonly ProfileItem _node = context.Node;
private SingboxConfig _coreConfig = new();
#region public gen function #region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node) public RetResult GenerateClientConfigContent()
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
{ {
if (node == null if (_node == null
|| !node.IsValid()) || !_node.IsValid())
{ {
ret.Msg = ResUI.CheckServerSettings; ret.Msg = ResUI.CheckServerSettings;
return ret; return ret;
} }
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{ {
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret; return ret;
} }
ret.Msg = ResUI.InitialConfiguration; ret.Msg = ResUI.InitialConfiguration;
if (node.ConfigType.IsGroupType())
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty()) if (result.IsNullOrEmpty())
{ {
@ -45,44 +36,31 @@ public partial class CoreConfigSingboxService(Config config)
return ret; return ret;
} }
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result); _coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null) if (_coreConfig == null)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenLog(singboxConfig); GenLog();
await GenInbounds(singboxConfig); GenInbounds();
if (node.ConfigType == EConfigType.WireGuard) GenOutbounds();
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
await GenMoreOutbounds(node, singboxConfig); GenRouting();
await GenRouting(singboxConfig); GenDns();
await GenDns(node, singboxConfig); GenExperimental();
await GenExperimental(singboxConfig); ConvertGeo2Ruleset();
await ConvertGeo2Ruleset(singboxConfig);
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true; ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig); ret.Data = ApplyFullConfigTemplate();
return ret; return ret;
} }
catch (Exception ex) catch (Exception ex)
@ -93,17 +71,11 @@ public partial class CoreConfigSingboxService(Config config)
} }
} }
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds) public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
{ {
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration; ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
@ -114,8 +86,8 @@ public partial class CoreConfigSingboxService(Config config)
return ret; return ret;
} }
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result); _coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null) if (_coreConfig == null)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
@ -133,10 +105,10 @@ public partial class CoreConfigSingboxService(Config config)
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
await GenLog(singboxConfig); GenLog();
//GenDns(new(), singboxConfig); GenMinimizedDns();
singboxConfig.inbounds.Clear(); _coreConfig.inbounds.Clear();
singboxConfig.outbounds.RemoveAt(0); _coreConfig.outbounds.RemoveAt(0);
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
@ -150,8 +122,9 @@ public partial class CoreConfigSingboxService(Config config)
{ {
continue; continue;
} }
var item = await AppManager.Instance.GetProfileItem(it.IndexId); var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
if (item is null || item.IsComplex() || !item.IsValid()) var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
{ {
continue; continue;
} }
@ -190,26 +163,11 @@ public partial class CoreConfigSingboxService(Config config)
type = EInboundProtocol.mixed.ToString(), type = EInboundProtocol.mixed.ToString(),
}; };
inbound.tag = inbound.type + inbound.listen_port.ToString(); inbound.tag = inbound.type + inbound.listen_port.ToString();
singboxConfig.inbounds.Add(inbound); _coreConfig.inbounds.Add(inbound);
//outbound
var server = await GenServer(item);
if (server is null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
var tag = Global.ProxyTag + inbound.listen_port.ToString(); var tag = Global.ProxyTag + inbound.listen_port.ToString();
server.tag = tag; var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag);
if (server is Endpoints4Sbox endpoint) FillRangeProxy(serverList, _coreConfig, false);
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
//rule //rule
Rule4Sbox rule = new() Rule4Sbox rule = new()
@ -217,25 +175,11 @@ public partial class CoreConfigSingboxService(Config config)
inbound = new List<string> { inbound.tag }, inbound = new List<string> { inbound.tag },
outbound = tag outbound = tag
}; };
singboxConfig.route.rules.Add(rule); _coreConfig.route.rules.Add(rule);
} }
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (rawDNSItem != null && rawDNSItem.Enabled == true)
{
await GenDnsDomainsCompatible(singboxConfig, rawDNSItem);
}
else
{
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
}
singboxConfig.route.default_domain_resolver = new()
{
server = Global.SingboxLocalDNSTag,
};
ret.Success = true; ret.Success = true;
ret.Data = JsonUtils.Serialize(singboxConfig); ret.Data = JsonUtils.Serialize(_coreConfig);
return ret; return ret;
} }
catch (Exception ex) catch (Exception ex)
@ -246,20 +190,20 @@ public partial class CoreConfigSingboxService(Config config)
} }
} }
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port) public RetResult GenerateClientSpeedtestConfig(int port)
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
{ {
if (node == null if (_node == null
|| !node.IsValid()) || !_node.IsValid())
{ {
ret.Msg = ResUI.CheckServerSettings; ret.Msg = ResUI.CheckServerSettings;
return ret; return ret;
} }
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{ {
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret; return ret;
} }
@ -272,44 +216,20 @@ public partial class CoreConfigSingboxService(Config config)
return ret; return ret;
} }
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result); _coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null) if (_coreConfig == null)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenLog(singboxConfig); GenLog();
if (node.ConfigType == EConfigType.WireGuard) GenOutbounds();
{ GenMinimizedDns();
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
await GenMoreOutbounds(node, singboxConfig);
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (item != null && item.Enabled == true)
{
await GenDnsDomainsCompatible(singboxConfig, item);
}
else
{
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
}
singboxConfig.route.default_domain_resolver = new()
{
server = Global.SingboxLocalDNSTag,
};
singboxConfig.route.rules.Clear(); _coreConfig.route.rules.Clear();
singboxConfig.inbounds.Clear(); _coreConfig.inbounds.Clear();
singboxConfig.inbounds.Add(new() _coreConfig.inbounds.Add(new()
{ {
tag = $"{EInboundProtocol.mixed}{port}", tag = $"{EInboundProtocol.mixed}{port}",
listen = Global.Loopback, listen = Global.Loopback,
@ -319,202 +239,7 @@ public partial class CoreConfigSingboxService(Config config)
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true; ret.Success = true;
ret.Data = JsonUtils.Serialize(singboxConfig); ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
singboxConfig.outbounds.RemoveAt(0);
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(parentNode, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
singboxConfig.outbounds.RemoveAt(0);
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(parentNode, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
if (node == null || fileName is null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
try
{
if (node == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (File.Exists(fileName))
{
File.Delete(fileName);
}
var addressFileName = node.Address;
if (addressFileName.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
if (!File.Exists(addressFileName))
{
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
}
if (!File.Exists(addressFileName))
{
ret.Msg = ResUI.FailedReadConfiguration + "1";
return ret;
}
if (node.Address == Global.CoreMultipleLoadConfigFileName)
{
var txtFile = File.ReadAllText(addressFileName);
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(txtFile);
if (singboxConfig == null)
{
File.Copy(addressFileName, fileName);
}
else
{
await GenInbounds(singboxConfig);
await GenExperimental(singboxConfig);
var content = JsonUtils.Serialize(singboxConfig, true);
await File.WriteAllTextAsync(fileName, content);
}
}
else
{
File.Copy(addressFileName, fileName);
}
//check again
if (!File.Exists(fileName))
{
ret.Msg = ResUI.FailedReadConfiguration + "2";
return ret;
}
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
return ret; return ret;
} }
catch (Exception ex) catch (Exception ex)

View file

@ -2,29 +2,29 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
{ {
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig) private string ApplyFullConfigTemplate()
{ {
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); var fullConfigTemplate = context.FullConfigTemplate;
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled) if (fullConfigTemplate is not { Enabled: true })
{ {
return JsonUtils.Serialize(singboxConfig); return JsonUtils.Serialize(_coreConfig);
} }
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config; var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
if (fullConfigTemplateItem.IsNullOrEmpty()) if (fullConfigTemplateItem.IsNullOrEmpty())
{ {
return JsonUtils.Serialize(singboxConfig); return JsonUtils.Serialize(_coreConfig);
} }
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem); var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
if (fullConfigTemplateNode == null) if (fullConfigTemplateNode == null)
{ {
return JsonUtils.Serialize(singboxConfig); return JsonUtils.Serialize(_coreConfig);
} }
// Process outbounds // Process outbounds
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray(); var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in singboxConfig.outbounds) foreach (var outbound in _coreConfig.outbounds)
{ {
if (outbound.type.ToLower() is "direct" or "block") if (outbound.type.ToLower() is "direct" or "block")
{ {
@ -42,10 +42,10 @@ public partial class CoreConfigSingboxService
fullConfigTemplateNode["outbounds"] = customOutboundsNode; fullConfigTemplateNode["outbounds"] = customOutboundsNode;
// Process endpoints // Process endpoints
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0) if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0)
{ {
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray(); var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
foreach (var endpoint in singboxConfig.endpoints) foreach (var endpoint in _coreConfig.endpoints)
{ {
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
{ {
@ -56,6 +56,6 @@ public partial class CoreConfigSingboxService
fullConfigTemplateNode["endpoints"] = customEndpointsNode; fullConfigTemplateNode["endpoints"] = customEndpointsNode;
} }
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); return JsonUtils.Serialize(fullConfigTemplateNode);
} }
} }

View file

@ -2,25 +2,25 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
{ {
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig) private void GenDns()
{ {
try try
{ {
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); var item = context.RawDnsItem;
if (item != null && item.Enabled == true) if (item is { Enabled: true })
{ {
return await GenDnsCompatible(node, singboxConfig); GenDnsCustom();
return;
} }
var simpleDNSItem = _config.SimpleDNSItem; GenDnsServers();
await GenDnsServers(node, singboxConfig, simpleDNSItem); GenDnsRules();
await GenDnsRules(node, singboxConfig, simpleDNSItem);
singboxConfig.dns ??= new Dns4Sbox(); _coreConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.independent_cache = true; _coreConfig.dns.independent_cache = true;
// final dns // final dns
var routing = await ConfigHandler.GetDefaultRouting(_config); var routing = context.RoutingItem;
var useDirectDns = false; var useDirectDns = false;
if (routing != null) if (routing != null)
{ {
@ -32,35 +32,34 @@ public partial class CoreConfigSingboxService
lastRule.Network == "tcp,udp" || lastRule.Network == "tcp,udp" ||
lastRule.Ip?.Contains("0.0.0.0/0") == true); lastRule.Ip?.Contains("0.0.0.0/0") == true);
} }
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag; _coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false) var simpleDnsItem = context.SimpleDnsItem;
if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{ {
singboxConfig.dns.rules.Add(new() _coreConfig.dns.rules.Add(new()
{ {
server = Global.SingboxFakeDNSTag, server = Global.SingboxFakeDNSTag,
query_type = new List<int> { 1, 28 }, // A and AAAA query_type = new List<int> { 1, 28 }, // A and AAAA
rewrite_ttl = 1, rewrite_ttl = 1,
}); });
} }
await GenOutboundDnsRule(node, singboxConfig);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private async Task<int> GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) private void GenDnsServers()
{ {
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem); var simpleDnsItem = context.SimpleDnsItem;
var finalDns = GenBootstrapDns();
var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS); var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First());
directDns.tag = Global.SingboxDirectDNSTag; directDns.tag = Global.SingboxDirectDNSTag;
directDns.domain_resolver = Global.SingboxLocalDNSTag; directDns.domain_resolver = Global.SingboxLocalDNSTag;
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS); var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
remoteDns.tag = Global.SingboxRemoteDNSTag; remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag; remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxLocalDNSTag; remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
@ -71,12 +70,12 @@ public partial class CoreConfigSingboxService
type = "hosts", type = "hosts",
predefined = new(), predefined = new(),
}; };
if (simpleDNSItem.AddCommonHosts == true) if (simpleDnsItem.AddCommonHosts == true)
{ {
hostsDns.predefined = Global.PredefinedHosts; hostsDns.predefined = Global.PredefinedHosts;
} }
if (simpleDNSItem.UseSystemHosts == true) if (simpleDnsItem.UseSystemHosts == true)
{ {
var systemHosts = Utils.GetSystemHosts(); var systemHosts = Utils.GetSystemHosts();
if (systemHosts != null && systemHosts.Count > 0) if (systemHosts != null && systemHosts.Count > 0)
@ -88,9 +87,9 @@ public partial class CoreConfigSingboxService
} }
} }
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts)) foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{ {
hostsDns.predefined[kvp.Key] = kvp.Value.Where(s => Utils.IsIpAddress(s)).ToList(); hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList();
} }
foreach (var host in hostsDns.predefined) foreach (var host in hostsDns.predefined)
@ -109,14 +108,14 @@ public partial class CoreConfigSingboxService
} }
} }
singboxConfig.dns ??= new Dns4Sbox(); _coreConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= []; _coreConfig.dns.servers ??= [];
singboxConfig.dns.servers.Add(remoteDns); _coreConfig.dns.servers.Add(remoteDns);
singboxConfig.dns.servers.Add(directDns); _coreConfig.dns.servers.Add(directDns);
singboxConfig.dns.servers.Add(hostsDns); _coreConfig.dns.servers.Add(hostsDns);
// fake ip // fake ip
if (simpleDNSItem.FakeIP == true) if (simpleDnsItem.FakeIP == true)
{ {
var fakeip = new Server4Sbox var fakeip = new Server4Sbox
{ {
@ -125,68 +124,50 @@ public partial class CoreConfigSingboxService
inet4_range = "198.18.0.0/15", inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18", inet6_range = "fc00::/18",
}; };
singboxConfig.dns.servers.Add(fakeip); _coreConfig.dns.servers.Add(fakeip);
}
} }
// ech private Server4Sbox GenBootstrapDns()
var (_, dnsServer) = ParseEchParam(node?.EchConfigList);
if (dnsServer is not null)
{ {
dnsServer.tag = Global.SingboxEchDNSTag; var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First());
if (dnsServer.server is not null
&& hostsDns.predefined.ContainsKey(dnsServer.server))
{
dnsServer.domain_resolver = Global.SingboxHostsDNSTag;
}
else
{
dnsServer.domain_resolver = Global.SingboxLocalDNSTag;
}
singboxConfig.dns.servers.Add(dnsServer);
}
else if (node?.ConfigType.IsGroupType() == true)
{
var echDnsObject = JsonUtils.DeepCopy(directDns);
echDnsObject.tag = Global.SingboxEchDNSTag;
singboxConfig.dns.servers.Add(echDnsObject);
}
return await Task.FromResult(0);
}
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
{
var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS);
finalDns.tag = Global.SingboxLocalDNSTag; finalDns.tag = Global.SingboxLocalDNSTag;
singboxConfig.dns ??= new Dns4Sbox(); _coreConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>(); _coreConfig.dns.servers ??= [];
singboxConfig.dns.servers.Add(finalDns); _coreConfig.dns.servers.Add(finalDns);
return await Task.FromResult(finalDns); return finalDns;
} }
private async Task<int> GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) private void GenDnsRules()
{ {
singboxConfig.dns ??= new Dns4Sbox(); var simpleDnsItem = context.SimpleDnsItem;
singboxConfig.dns.rules ??= new List<Rule4Sbox>(); _coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.rules ??= [];
singboxConfig.dns.rules.AddRange(new[] _coreConfig.dns.rules.AddRange(new[]
{ {
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag }, new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
new Rule4Sbox new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
domain = context.ProtectDomainList.ToList(),
},
new Rule4Sbox
{ {
server = Global.SingboxRemoteDNSTag, server = Global.SingboxRemoteDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy), strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
clash_mode = ERuleMode.Global.ToString() clash_mode = ERuleMode.Global.ToString()
}, },
new Rule4Sbox new Rule4Sbox
{ {
server = Global.SingboxDirectDNSTag, server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom), strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
clash_mode = ERuleMode.Direct.ToString() clash_mode = ERuleMode.Direct.ToString()
} }
}); });
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts)) foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{ {
var predefined = kvp.Value.First(); var predefined = kvp.Value.First();
if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined)) if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined))
@ -197,7 +178,7 @@ public partial class CoreConfigSingboxService
{ {
// xray syntactic sugar for predefined // xray syntactic sugar for predefined
// etc. #0 -> NOERROR // etc. #0 -> NOERROR
singboxConfig.dns.rules.Add(new() _coreConfig.dns.rules.Add(new()
{ {
query_type = [1, 28], query_type = [1, 28],
domain = [kvp.Key], domain = [kvp.Key],
@ -225,38 +206,13 @@ public partial class CoreConfigSingboxService
}; };
if (ParseV2Domain(kvp.Key, rule)) if (ParseV2Domain(kvp.Key, rule))
{ {
singboxConfig.dns.rules.Add(rule); _coreConfig.dns.rules.Add(rule);
} }
} }
var (ech, _) = ParseEchParam(node?.EchConfigList); if (simpleDnsItem.BlockBindingQuery == true)
if (ech is not null)
{ {
var echDomain = ech.query_server_name ?? node?.Sni; _coreConfig.dns.rules.Add(new()
singboxConfig.dns.rules.Add(new()
{
query_type = [64, 65],
server = Global.SingboxEchDNSTag,
domain = echDomain is not null ? new List<string> { echDomain } : null,
});
}
else if (node?.ConfigType.IsGroupType() == true)
{
var queryServerNames = (await GroupProfileManager.GetAllChildEchQuerySni(node)).ToList();
if (queryServerNames.Count > 0)
{
singboxConfig.dns.rules.Add(new()
{
query_type = [64, 65],
server = Global.SingboxEchDNSTag,
domain = queryServerNames,
});
}
}
if (simpleDNSItem.BlockBindingQuery == true)
{
singboxConfig.dns.rules.Add(new()
{ {
query_type = [64, 65], query_type = [64, 65],
action = "predefined", action = "predefined",
@ -264,7 +220,7 @@ public partial class CoreConfigSingboxService
}); });
} }
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true) if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
{ {
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName)); var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
fakeipFilterRule.invert = true; fakeipFilterRule.invert = true;
@ -284,13 +240,13 @@ public partial class CoreConfigSingboxService
] ]
}; };
singboxConfig.dns.rules.Add(rule4Fake); _coreConfig.dns.rules.Add(rule4Fake);
} }
var routing = await ConfigHandler.GetDefaultRouting(_config); var routing = context.RoutingItem;
if (routing == null) if (routing == null)
{ {
return 0; return;
} }
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? []; var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
@ -298,9 +254,9 @@ public partial class CoreConfigSingboxService
var expectedIPsRegions = new List<string>(); var expectedIPsRegions = new List<string>();
var regionNames = new HashSet<string>(); var regionNames = new HashSet<string>();
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs))
{ {
var ipItems = simpleDNSItem.DirectExpectedIPs var ipItems = simpleDnsItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim()) .Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s)) .Where(s => !string.IsNullOrEmpty(s))
@ -348,7 +304,7 @@ public partial class CoreConfigSingboxService
if (item.OutboundTag == Global.DirectTag) if (item.OutboundTag == Global.DirectTag)
{ {
rule.server = Global.SingboxDirectDNSTag; rule.server = Global.SingboxDirectDNSTag;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom); rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0) if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
{ {
@ -373,31 +329,46 @@ public partial class CoreConfigSingboxService
} }
else else
{ {
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false) if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{ {
var rule4Fake = JsonUtils.DeepCopy(rule); var rule4Fake = JsonUtils.DeepCopy(rule);
rule4Fake.server = Global.SingboxFakeDNSTag; rule4Fake.server = Global.SingboxFakeDNSTag;
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
rule4Fake.rewrite_ttl = 1; rule4Fake.rewrite_ttl = 1;
singboxConfig.dns.rules.Add(rule4Fake); _coreConfig.dns.rules.Add(rule4Fake);
} }
rule.server = Global.SingboxRemoteDNSTag; rule.server = Global.SingboxRemoteDNSTag;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy); rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy);
} }
singboxConfig.dns.rules.Add(rule); _coreConfig.dns.rules.Add(rule);
}
} }
return 0; private void GenMinimizedDns()
{
GenDnsServers();
foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList())
{
_coreConfig.dns.servers.Remove(server);
}
_coreConfig.dns ??= new();
_coreConfig.dns.rules ??= [];
_coreConfig.dns.rules.Clear();
_coreConfig.dns.final = Global.SingboxDirectDNSTag;
_coreConfig.route.default_domain_resolver = new()
{
server = Global.SingboxDirectDNSTag,
};
} }
private async Task<int> GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig) private void GenDnsCustom()
{ {
try try
{ {
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); var item = context.RawDnsItem;
var strDNS = string.Empty; var strDNS = string.Empty;
if (_config.TunModeItem.EnableTun) if (context.IsTunEnabled)
{ {
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS; strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
} }
@ -409,61 +380,33 @@ public partial class CoreConfigSingboxService
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS); var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
if (dns4Sbox is null) if (dns4Sbox is null)
{ {
return 0; return;
} }
singboxConfig.dns = dns4Sbox; _coreConfig.dns = dns4Sbox;
if (dns4Sbox.servers?.Count > 0 &&
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) dns4Sbox.servers.First().address.IsNullOrEmpty())
{ {
await GenDnsDomainsCompatible(singboxConfig, item); GenDnsProtectCustom();
} }
else else
{ {
await GenDnsDomainsLegacyCompatible(singboxConfig, item); GenDnsProtectCustomLegacy();
} }
await GenOutboundDnsRule(node, singboxConfig);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private async Task<int> GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem) private void GenDnsProtectCustom()
{ {
var dns4Sbox = singboxConfig.dns ?? new(); var dnsItem = context.RawDnsItem;
var dns4Sbox = _coreConfig.dns ?? new();
dns4Sbox.servers ??= []; dns4Sbox.servers ??= [];
dns4Sbox.rules ??= []; dns4Sbox.rules ??= [];
var tag = Global.SingboxLocalDNSTag; var tag = Global.SingboxLocalDNSTag;
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
var localDnsServer = ParseDnsAddress(finalDnsAddress);
localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer);
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
}
private async Task<int> GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
{
var dns4Sbox = singboxConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = Global.SingboxLocalDNSTag;
dns4Sbox.servers.Add(new()
{
tag = tag,
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
detour = Global.DirectTag,
strategy = string.IsNullOrEmpty(dnsItem?.DomainStrategy4Freedom) ? null : dnsItem?.DomainStrategy4Freedom,
});
dns4Sbox.rules.Insert(0, new() dns4Sbox.rules.Insert(0, new()
{ {
server = tag, server = tag,
@ -475,56 +418,39 @@ public partial class CoreConfigSingboxService
clash_mode = ERuleMode.Global.ToString() clash_mode = ERuleMode.Global.ToString()
}); });
var lstDomain = singboxConfig.outbounds var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
.Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server))
.Select(t => t.server) var localDnsServer = ParseDnsAddress(finalDnsAddress);
.Distinct() localDnsServer.tag = tag;
.ToList();
if (lstDomain != null && lstDomain.Count > 0) dns4Sbox.servers.Add(localDnsServer);
{ dns4Sbox.rules.Insert(0, BuildProtectDomainRule());
dns4Sbox.rules.Insert(0, new()
{ _coreConfig.dns = dns4Sbox;
server = tag,
domain = lstDomain
});
} }
singboxConfig.dns = dns4Sbox; private void GenDnsProtectCustomLegacy()
return await Task.FromResult(0); {
GenDnsProtectCustom();
var localDnsServer = _coreConfig.dns?.servers?.FirstOrDefault(s => s.tag == Global.SingboxLocalDNSTag);
if (localDnsServer == null)
{
return;
}
localDnsServer.type = null;
localDnsServer.server = null;
var dnsItem = context.RawDnsItem;
localDnsServer.address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
} }
private async Task<int> GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig) private Rule4Sbox BuildProtectDomainRule()
{ {
if (node == null) return new()
{
return 0;
}
List<string> domain = new();
if (Utils.IsDomain(node.Address)) // normal outbound
{
domain.Add(node.Address);
}
if (node.Address == Global.Loopback && node.SpiderX.IsNotEmpty()) // Tun2SocksAddress
{
domain.AddRange(Utils.String2List(node.SpiderX)
.Where(Utils.IsDomain)
.Distinct()
.ToList());
}
if (domain.Count == 0)
{
return 0;
}
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
{ {
server = Global.SingboxLocalDNSTag, server = Global.SingboxLocalDNSTag,
domain = domain, domain = context.ProtectDomainList.ToList(),
}); };
return await Task.FromResult(0);
} }
private static Server4Sbox? ParseDnsAddress(string address) private static Server4Sbox? ParseDnsAddress(string address)

View file

@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
{ {
private async Task<int> GenInbounds(SingboxConfig singboxConfig) private void GenInbounds()
{ {
try try
{ {
var listen = "0.0.0.0"; var listen = "0.0.0.0";
singboxConfig.inbounds = []; _coreConfig.inbounds = [];
if (!_config.TunModeItem.EnableTun if (!_config.TunModeItem.EnableTun
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box)) || (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box))
@ -18,23 +18,23 @@ public partial class CoreConfigSingboxService
tag = EInboundProtocol.socks.ToString(), tag = EInboundProtocol.socks.ToString(),
listen = Global.Loopback, listen = Global.Loopback,
}; };
singboxConfig.inbounds.Add(inbound); _coreConfig.inbounds.Add(inbound);
inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
if (_config.Inbound.First().SecondLocalPortEnabled) if (_config.Inbound.First().SecondLocalPortEnabled)
{ {
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true); var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true);
singboxConfig.inbounds.Add(inbound2); _coreConfig.inbounds.Add(inbound2);
} }
if (_config.Inbound.First().AllowLANConn) if (_config.Inbound.First().AllowLANConn)
{ {
if (_config.Inbound.First().NewPort4LAN) if (_config.Inbound.First().NewPort4LAN)
{ {
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true); var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true);
inbound3.listen = listen; inbound3.listen = listen;
singboxConfig.inbounds.Add(inbound3); _coreConfig.inbounds.Add(inbound3);
//auth //auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
@ -71,17 +71,16 @@ public partial class CoreConfigSingboxService
tunInbound.address = ["172.18.0.1/30"]; tunInbound.address = ["172.18.0.1/30"];
} }
singboxConfig.inbounds.Add(tunInbound); _coreConfig.inbounds.Add(tunInbound);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return await Task.FromResult(0);
} }
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) private Inbound4Sbox BuildInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
{ {
var inbound = JsonUtils.DeepCopy(inItem); var inbound = JsonUtils.DeepCopy(inItem);
inbound.tag = protocol.ToString(); inbound.tag = protocol.ToString();

View file

@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
{ {
private async Task<int> GenLog(SingboxConfig singboxConfig) private void GenLog()
{ {
try try
{ {
@ -11,11 +11,11 @@ public partial class CoreConfigSingboxService
case "debug": case "debug":
case "info": case "info":
case "error": case "error":
singboxConfig.log.level = _config.CoreBasicItem.Loglevel; _coreConfig.log.level = _config.CoreBasicItem.Loglevel;
break; break;
case "warning": case "warning":
singboxConfig.log.level = "warn"; _coreConfig.log.level = "warn";
break; break;
default: default:
@ -23,18 +23,17 @@ public partial class CoreConfigSingboxService
} }
if (_config.CoreBasicItem.Loglevel == Global.None) if (_config.CoreBasicItem.Loglevel == Global.None)
{ {
singboxConfig.log.disabled = true; _coreConfig.log.disabled = true;
} }
if (_config.CoreBasicItem.LogEnabled) if (_config.CoreBasicItem.LogEnabled)
{ {
var dtNow = DateTime.Now; var dtNow = DateTime.Now;
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); _coreConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return await Task.FromResult(0);
} }
} }

View file

@ -2,23 +2,23 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
{ {
private async Task<int> GenRouting(SingboxConfig singboxConfig) private void GenRouting()
{ {
try try
{ {
singboxConfig.route.final = Global.ProxyTag; _coreConfig.route.final = Global.ProxyTag;
var simpleDnsItem = _config.SimpleDNSItem; var simpleDnsItem = context.SimpleDnsItem;
var defaultDomainResolverTag = Global.SingboxDirectDNSTag; var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom); var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); var rawDNSItem = context.RawDnsItem;
if (rawDNSItem is { Enabled: true }) if (rawDNSItem is { Enabled: true })
{ {
defaultDomainResolverTag = Global.SingboxLocalDNSTag; defaultDomainResolverTag = Global.SingboxLocalDNSTag;
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom; directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
} }
singboxConfig.route.default_domain_resolver = new() _coreConfig.route.default_domain_resolver = new()
{ {
server = defaultDomainResolverTag, server = defaultDomainResolverTag,
strategy = directDnsStrategy strategy = directDnsStrategy
@ -26,23 +26,23 @@ public partial class CoreConfigSingboxService
if (_config.TunModeItem.EnableTun) if (_config.TunModeItem.EnableTun)
{ {
singboxConfig.route.auto_detect_interface = true; _coreConfig.route.auto_detect_interface = true;
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName)); var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
if (tunRules != null) if (tunRules != null)
{ {
singboxConfig.route.rules.AddRange(tunRules); _coreConfig.route.rules.AddRange(tunRules);
} }
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe); var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
port = new() { 53 }, port = [53],
action = "hijack-dns", action = "hijack-dns",
process_name = lstDnsExe process_name = lstDnsExe
}); });
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
outbound = Global.DirectTag, outbound = Global.DirectTag,
process_name = lstDirectExe process_name = lstDirectExe
@ -51,66 +51,62 @@ public partial class CoreConfigSingboxService
if (_config.Inbound.First().SniffingEnabled) if (_config.Inbound.First().SniffingEnabled)
{ {
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
action = "sniff" action = "sniff"
}); });
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
protocol = new() { "dns" }, protocol = ["dns"],
action = "hijack-dns" action = "hijack-dns"
}); });
} }
else else
{ {
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
port = new() { 53 }, port = [53],
network = new() { "udp" }, network = ["udp"],
action = "hijack-dns" action = "hijack-dns"
}); });
} }
var hostsDomains = new List<string>(); var hostsDomains = new List<string>();
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); if (rawDNSItem is not { Enabled: true })
if (dnsItem == null || !dnsItem.Enabled)
{ {
var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts); var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts);
hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key)); hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key));
if (simpleDnsItem.UseSystemHosts == true) if (simpleDnsItem.UseSystemHosts == true)
{ {
var systemHostsMap = Utils.GetSystemHosts(); var systemHostsMap = Utils.GetSystemHosts();
foreach (var kvp in systemHostsMap) hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key));
{
hostsDomains.Add(kvp.Key);
}
} }
} }
if (hostsDomains.Count > 0) if (hostsDomains.Count > 0)
{ {
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
action = "resolve", action = "resolve",
domain = hostsDomains, domain = hostsDomains,
}); });
} }
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
outbound = Global.DirectTag, outbound = Global.DirectTag,
clash_mode = ERuleMode.Direct.ToString() clash_mode = ERuleMode.Direct.ToString()
}); });
singboxConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
outbound = Global.ProxyTag, outbound = Global.ProxyTag,
clash_mode = ERuleMode.Global.ToString() clash_mode = ERuleMode.Global.ToString()
}); });
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty(); var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); var routing = context.RoutingItem;
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) if (routing.DomainStrategy4Singbox.IsNotEmpty())
{ {
domainStrategy = defaultRouting.DomainStrategy4Singbox; domainStrategy = routing.DomainStrategy4Singbox;
} }
var resolveRule = new Rule4Sbox var resolveRule = new Rule4Sbox
{ {
@ -119,10 +115,9 @@ public partial class CoreConfigSingboxService
}; };
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand) if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
{ {
singboxConfig.route.rules.Add(resolveRule); _coreConfig.route.rules.Add(resolveRule);
} }
var routing = await ConfigHandler.GetDefaultRouting(_config);
var ipRules = new List<RulesItem>(); var ipRules = new List<RulesItem>();
if (routing != null) if (routing != null)
{ {
@ -139,7 +134,7 @@ public partial class CoreConfigSingboxService
continue; continue;
} }
await GenRoutingUserRule(item1, singboxConfig); GenRoutingUserRule(item1);
if (item1.Ip?.Count > 0) if (item1.Ip?.Count > 0)
{ {
@ -149,10 +144,10 @@ public partial class CoreConfigSingboxService
} }
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch) if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
{ {
singboxConfig.route.rules.Add(resolveRule); _coreConfig.route.rules.Add(resolveRule);
foreach (var item2 in ipRules) foreach (var item2 in ipRules)
{ {
await GenRoutingUserRule(item2, singboxConfig); GenRoutingUserRule(item2);
} }
} }
} }
@ -160,10 +155,9 @@ public partial class CoreConfigSingboxService
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe) private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
{ {
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@ -187,20 +181,22 @@ public partial class CoreConfigSingboxService
} }
} }
lstDnsExe = new List<string>(dnsExeSet); var lstDnsExe = new List<string>(dnsExeSet);
lstDirectExe = new List<string>(directExeSet); var lstDirectExe = new List<string>(directExeSet);
return (lstDnsExe, lstDirectExe);
} }
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig) private void GenRoutingUserRule(RulesItem? item)
{ {
try try
{ {
if (item == null) if (item == null)
{ {
return 0; return;
} }
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag);
var rules = singboxConfig.route.rules; var rules = _coreConfig.route.rules;
var rule = new Rule4Sbox(); var rule = new Rule4Sbox();
if (item.OutboundTag == "block") if (item.OutboundTag == "block")
@ -326,10 +322,9 @@ public partial class CoreConfigSingboxService
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return await Task.FromResult(0);
} }
private bool ParseV2Domain(string domain, Rule4Sbox rule) private static bool ParseV2Domain(string domain, Rule4Sbox rule)
{ {
if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:")) if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
{ {
@ -368,7 +363,7 @@ public partial class CoreConfigSingboxService
return true; return true;
} }
private bool ParseV2Address(string address, Rule4Sbox rule) private static bool ParseV2Address(string address, Rule4Sbox rule)
{ {
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:")) if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
{ {
@ -401,14 +396,14 @@ public partial class CoreConfigSingboxService
return true; return true;
} }
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig) private string GenRoutingUserRuleOutbound(string outboundTag)
{ {
if (Global.OutboundTags.Contains(outboundTag)) if (Global.OutboundTags.Contains(outboundTag))
{ {
return outboundTag; return outboundTag;
} }
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
if (node == null if (node == null
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType) || (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
@ -418,39 +413,15 @@ public partial class CoreConfigSingboxService
} }
var tag = $"{node.IndexId}-{Global.ProxyTag}"; var tag = $"{node.IndexId}-{Global.ProxyTag}";
if (singboxConfig.outbounds.Any(o => o.tag == tag) if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag))
|| (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag))) || (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag))))
{ {
return tag; return tag;
} }
if (node.ConfigType.IsGroupType()) var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
{ FillRangeProxy(proxyOutbounds, _coreConfig, false);
var ret = await GenGroupOutbound(node, singboxConfig, tag);
if (ret == 0)
{
return tag; return tag;
} }
return Global.ProxyTag;
}
var server = await GenServer(node);
if (server is null)
{
return Global.ProxyTag;
}
server.tag = tag;
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
return server.tag;
}
} }

View file

@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
{ {
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig) private void ConvertGeo2Ruleset()
{ {
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set) static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
{ {
@ -16,14 +16,14 @@ public partial class CoreConfigSingboxService
var ruleSets = new List<string>(); var ruleSets = new List<string>();
//convert route geosite & geoip to ruleset //convert route geosite & geoip to ruleset
foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
rule.geosite = null; rule.geosite = null;
AddRuleSets(ruleSets, rule.rule_set); AddRuleSets(ruleSets, rule.rule_set);
} }
foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
@ -32,24 +32,24 @@ public partial class CoreConfigSingboxService
} }
//convert dns geosite & geoip to ruleset //convert dns geosite & geoip to ruleset
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
rule.geosite = null; rule.geosite = null;
} }
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
rule.geoip = null; rule.geoip = null;
} }
foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
{ {
AddRuleSets(ruleSets, dnsRule.rule_set); AddRuleSets(ruleSets, dnsRule.rule_set);
} }
//rules in rules //rules in rules
foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) foreach (var item in _coreConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
{ {
foreach (var item2 in item ?? []) foreach (var item2 in item ?? [])
{ {
@ -60,7 +60,7 @@ public partial class CoreConfigSingboxService
//load custom ruleset file //load custom ruleset file
List<Ruleset4Sbox> customRulesets = []; List<Ruleset4Sbox> customRulesets = [];
var routing = await ConfigHandler.GetDefaultRouting(_config); var routing = context.RoutingItem;
if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
{ {
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
@ -78,7 +78,7 @@ public partial class CoreConfigSingboxService
var localSrss = Utils.GetBinPath("srss"); var localSrss = Utils.GetBinPath("srss");
//Add ruleset srs //Add ruleset srs
singboxConfig.route.rule_set = []; _coreConfig.route.rule_set = [];
foreach (var item in new HashSet<string>(ruleSets)) foreach (var item in new HashSet<string>(ruleSets))
{ {
if (item.IsNullOrEmpty()) if (item.IsNullOrEmpty())
@ -113,9 +113,7 @@ public partial class CoreConfigSingboxService
}; };
} }
} }
singboxConfig.route.rule_set.Add(customRuleset); _coreConfig.route.rule_set.Add(customRuleset);
} }
return 0;
} }
} }

View file

@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService public partial class CoreConfigSingboxService
{ {
private async Task<int> GenExperimental(SingboxConfig singboxConfig) private void GenExperimental()
{ {
//if (_config.guiItem.enableStatistics) //if (_config.guiItem.enableStatistics)
{ {
singboxConfig.experimental ??= new Experimental4Sbox(); _coreConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.clash_api = new Clash_Api4Sbox() _coreConfig.experimental.clash_api = new Clash_Api4Sbox()
{ {
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}", external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
}; };
@ -15,15 +15,13 @@ public partial class CoreConfigSingboxService
if (_config.CoreBasicItem.EnableCacheFile4Sbox) if (_config.CoreBasicItem.EnableCacheFile4Sbox)
{ {
singboxConfig.experimental ??= new Experimental4Sbox(); _coreConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.cache_file = new CacheFile4Sbox() _coreConfig.experimental.cache_file = new CacheFile4Sbox()
{ {
enabled = true, enabled = true,
path = Utils.GetBinPath("cache.db"), path = Utils.GetBinPath("cache.db"),
store_fakeip = _config.SimpleDNSItem.FakeIP == true store_fakeip = context.SimpleDnsItem.FakeIP == true
}; };
} }
return await Task.FromResult(0);
} }
} }

View file

@ -1,44 +1,35 @@
namespace ServiceLib.Services.CoreConfig; namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService(Config config) public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigV2rayService"; private static readonly string _tag = "CoreConfigV2rayService";
private readonly Config _config = context.AppConfig;
private readonly ProfileItem _node = context.Node;
private V2rayConfig _coreConfig = new();
#region public gen function #region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node) public RetResult GenerateClientConfigContent()
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
{ {
if (node == null if (_node == null
|| !node.IsValid()) || !_node.IsValid())
{ {
ret.Msg = ResUI.CheckServerSettings; ret.Msg = ResUI.CheckServerSettings;
return ret; return ret;
} }
if (node.GetNetwork() is nameof(ETransport.quic)) if (_node.GetNetwork() is nameof(ETransport.quic))
{ {
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret; return ret;
} }
ret.Msg = ResUI.InitialConfiguration; ret.Msg = ResUI.InitialConfiguration;
if (node.ConfigType.IsGroupType())
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty()) if (result.IsNullOrEmpty())
{ {
@ -46,30 +37,28 @@ public partial class CoreConfigV2rayService(Config config)
return ret; return ret;
} }
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result); _coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null) if (_coreConfig == null)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenLog(v2rayConfig); GenLog();
await GenInbounds(v2rayConfig); GenInbounds();
await GenOutbound(node, v2rayConfig.outbounds.First()); GenOutbounds();
await GenMoreOutbounds(node, v2rayConfig); GenRouting();
await GenRouting(v2rayConfig); GenDns();
await GenDns(node, v2rayConfig); GenStatistic();
await GenStatistic(v2rayConfig);
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true; ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig); ret.Data = ApplyFullConfigTemplate();
return ret; return ret;
} }
catch (Exception ex) catch (Exception ex)
@ -80,18 +69,11 @@ public partial class CoreConfigV2rayService(Config config)
} }
} }
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode) public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
{ {
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration; ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
@ -102,172 +84,8 @@ public partial class CoreConfigV2rayService(Config config)
return ret; return ret;
} }
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result); _coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null) if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
v2rayConfig.outbounds.RemoveAt(0);
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
//add rule
var rules = v2rayConfig.routing.rules;
if (rules?.Count > 0 && ((v2rayConfig.routing.balancers?.Count ?? 0) > 0))
{
var balancerTagSet = v2rayConfig.routing.balancers
.Select(b => b.tag)
.ToHashSet();
foreach (var rule in rules)
{
if (rule.outboundTag == null)
{
continue;
}
if (balancerTagSet.Contains(rule.outboundTag))
{
rule.balancerTag = rule.outboundTag;
rule.outboundTag = null;
continue;
}
var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix;
if (balancerTagSet.Contains(outboundWithSuffix))
{
rule.balancerTag = outboundWithSuffix;
rule.outboundTag = null;
}
}
}
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
v2rayConfig.routing.rules.Add(new()
{
ip = ["0.0.0.0/0", "::/0"],
balancerTag = defaultBalancerTag,
type = "field"
});
}
else
{
v2rayConfig.routing.rules.Add(new()
{
network = "tcp,udp",
balancerTag = defaultBalancerTag,
type = "field"
});
}
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
v2rayConfig.outbounds.RemoveAt(0);
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
@ -285,10 +103,10 @@ public partial class CoreConfigV2rayService(Config config)
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
await GenLog(v2rayConfig); GenLog();
v2rayConfig.inbounds.Clear(); _coreConfig.inbounds.Clear();
v2rayConfig.outbounds.Clear(); _coreConfig.outbounds.Clear();
v2rayConfig.routing.rules.Clear(); _coreConfig.routing.rules.Clear();
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
@ -302,8 +120,9 @@ public partial class CoreConfigV2rayService(Config config)
{ {
continue; continue;
} }
var item = await AppManager.Instance.GetProfileItem(it.IndexId); var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
if (item is null || item.IsComplex() || !item.IsValid()) var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
{ {
continue; continue;
} }
@ -342,27 +161,40 @@ public partial class CoreConfigV2rayService(Config config)
protocol = EInboundProtocol.mixed.ToString(), protocol = EInboundProtocol.mixed.ToString(),
}; };
inbound.tag = inbound.protocol + inbound.port.ToString(); inbound.tag = inbound.protocol + inbound.port.ToString();
v2rayConfig.inbounds.Add(inbound); _coreConfig.inbounds.Add(inbound);
var tag = Global.ProxyTag + inbound.port.ToString();
var isBalancer = false;
//outbound //outbound
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); var proxyOutbounds =
await GenOutbound(item, outbound); new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag);
outbound.tag = Global.ProxyTag + inbound.port.ToString(); _coreConfig.outbounds.AddRange(proxyOutbounds);
v2rayConfig.outbounds.Add(outbound); if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
{
isBalancer = true;
var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad, tag);
GenBalancer(multipleLoad, tag);
}
//rule //rule
RulesItem4Ray rule = new() RulesItem4Ray rule = new()
{ {
inboundTag = new List<string> { inbound.tag }, inboundTag = new List<string> { inbound.tag },
outboundTag = outbound.tag, outboundTag = tag,
type = "field" type = "field"
}; };
v2rayConfig.routing.rules.Add(rule); if (isBalancer)
{
rule.balancerTag = tag;
rule.outboundTag = null;
}
_coreConfig.routing.rules.Add(rule);
} }
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
ret.Success = true; ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig); ret.Data = JsonUtils.Serialize(_coreConfig);
return ret; return ret;
} }
catch (Exception ex) catch (Exception ex)
@ -373,21 +205,21 @@ public partial class CoreConfigV2rayService(Config config)
} }
} }
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port) public RetResult GenerateClientSpeedtestConfig(int port)
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
{ {
if (node == null if (_node == null
|| !node.IsValid()) || !_node.IsValid())
{ {
ret.Msg = ResUI.CheckServerSettings; ret.Msg = ResUI.CheckServerSettings;
return ret; return ret;
} }
if (node.GetNetwork() is nameof(ETransport.quic)) if (_node.GetNetwork() is nameof(ETransport.quic))
{ {
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret; return ret;
} }
@ -398,20 +230,19 @@ public partial class CoreConfigV2rayService(Config config)
return ret; return ret;
} }
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result); _coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null) if (_coreConfig == null)
{ {
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenLog(v2rayConfig); GenLog();
await GenOutbound(node, v2rayConfig.outbounds.First()); GenOutbounds();
await GenMoreOutbounds(node, v2rayConfig);
v2rayConfig.routing.rules.Clear(); _coreConfig.routing.rules.Clear();
v2rayConfig.inbounds.Clear(); _coreConfig.inbounds.Clear();
v2rayConfig.inbounds.Add(new() _coreConfig.inbounds.Add(new()
{ {
tag = $"{EInboundProtocol.socks}{port}", tag = $"{EInboundProtocol.socks}{port}",
listen = Global.Loopback, listen = Global.Loopback,
@ -421,7 +252,7 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true; ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig); ret.Data = JsonUtils.Serialize(_coreConfig);
return ret; return ret;
} }
catch (Exception ex) catch (Exception ex)

View file

@ -2,17 +2,17 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) private void GenObservatory(EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
{ {
// Collect all existing subject selectors from both observatories // Collect all existing subject selectors from both observatories
var subjectSelectors = new List<string>(); var subjectSelectors = new List<string>();
subjectSelectors.AddRange(v2rayConfig.burstObservatory?.subjectSelector ?? []); subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []);
subjectSelectors.AddRange(v2rayConfig.observatory?.subjectSelector ?? []); subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []);
// Case 1: exact match already exists -> nothing to do // Case 1: exact match already exists -> nothing to do
if (subjectSelectors.Any(baseTagName.StartsWith)) if (subjectSelectors.Any(baseTagName.StartsWith))
{ {
return await Task.FromResult(0); return;
} }
// Case 2: prefix match exists -> reuse it and move to the first position // Case 2: prefix match exists -> reuse it and move to the first position
@ -21,28 +21,28 @@ public partial class CoreConfigV2rayService
{ {
baseTagName = matched; baseTagName = matched;
if (v2rayConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true) if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
{ {
v2rayConfig.burstObservatory.subjectSelector.Remove(baseTagName); _coreConfig.burstObservatory.subjectSelector.Remove(baseTagName);
v2rayConfig.burstObservatory.subjectSelector.Insert(0, baseTagName); _coreConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
} }
if (v2rayConfig.observatory?.subjectSelector?.Contains(baseTagName) == true) if (_coreConfig.observatory?.subjectSelector?.Contains(baseTagName) == true)
{ {
v2rayConfig.observatory.subjectSelector.Remove(baseTagName); _coreConfig.observatory.subjectSelector.Remove(baseTagName);
v2rayConfig.observatory.subjectSelector.Insert(0, baseTagName); _coreConfig.observatory.subjectSelector.Insert(0, baseTagName);
} }
return await Task.FromResult(0); return;
} }
// Case 3: need to create or insert based on multipleLoad type // Case 3: need to create or insert based on multipleLoad type
if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback) if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
{ {
if (v2rayConfig.burstObservatory is null) if (_coreConfig.burstObservatory is null)
{ {
// Create new burst observatory with default ping config // Create new burst observatory with default ping config
v2rayConfig.burstObservatory = new BurstObservatory4Ray _coreConfig.burstObservatory = new BurstObservatory4Ray
{ {
subjectSelector = [baseTagName], subjectSelector = [baseTagName],
pingConfig = new() pingConfig = new()
@ -56,16 +56,16 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
v2rayConfig.burstObservatory.subjectSelector ??= new(); _coreConfig.burstObservatory.subjectSelector ??= new();
v2rayConfig.burstObservatory.subjectSelector.Add(baseTagName); _coreConfig.burstObservatory.subjectSelector.Add(baseTagName);
} }
} }
else if (multipleLoad is EMultipleLoad.LeastPing) else if (multipleLoad is EMultipleLoad.LeastPing)
{ {
if (v2rayConfig.observatory is null) if (_coreConfig.observatory is null)
{ {
// Create new observatory with default probe config // Create new observatory with default probe config
v2rayConfig.observatory = new Observatory4Ray _coreConfig.observatory = new Observatory4Ray
{ {
subjectSelector = [baseTagName], subjectSelector = [baseTagName],
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl, probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
@ -75,15 +75,13 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
v2rayConfig.observatory.subjectSelector ??= new(); _coreConfig.observatory.subjectSelector ??= new();
v2rayConfig.observatory.subjectSelector.Add(baseTagName); _coreConfig.observatory.subjectSelector.Add(baseTagName);
}
} }
} }
return await Task.FromResult(0); private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
}
private async Task<string> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
{ {
var strategyType = multipleLoad switch var strategyType = multipleLoad switch
{ {
@ -107,8 +105,7 @@ public partial class CoreConfigV2rayService
}, },
tag = balancerTag, tag = balancerTag,
}; };
v2rayConfig.routing.balancers ??= new(); _coreConfig.routing.balancers ??= new();
v2rayConfig.routing.balancers.Add(balancer); _coreConfig.routing.balancers.Add(balancer);
return await Task.FromResult(balancerTag);
} }
} }

View file

@ -2,24 +2,24 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig) private string ApplyFullConfigTemplate()
{ {
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray); var fullConfigTemplate = context.FullConfigTemplate;
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty()) if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
{ {
return JsonUtils.Serialize(v2rayConfig); return JsonUtils.Serialize(_coreConfig);
} }
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config); var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
if (fullConfigTemplateNode == null) if (fullConfigTemplateNode == null)
{ {
return JsonUtils.Serialize(v2rayConfig); return JsonUtils.Serialize(_coreConfig);
} }
// Handle balancer and rules modifications (for multiple load scenarios) // Handle balancer and rules modifications (for multiple load scenarios)
if (v2rayConfig.routing?.balancers?.Count > 0) if (_coreConfig.routing?.balancers?.Count > 0)
{ {
var balancer = v2rayConfig.routing.balancers.First(); var balancer = _coreConfig.routing.balancers.First();
// Modify existing rules in custom config // Modify existing rules in custom config
var rulesNode = fullConfigTemplateNode["routing"]?["rules"]; var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
@ -44,7 +44,7 @@ public partial class CoreConfigV2rayService
// Handle balancers - append instead of override // Handle balancers - append instead of override
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode) if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
{ {
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers) if (JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)) is JsonArray newBalancers)
{ {
foreach (var balancerNode in newBalancers) foreach (var balancerNode in newBalancers)
{ {
@ -54,33 +54,33 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)); fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers));
} }
} }
if (v2rayConfig.observatory != null) if (_coreConfig.observatory != null)
{ {
if (fullConfigTemplateNode["observatory"] == null) if (fullConfigTemplateNode["observatory"] == null)
{ {
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory)); fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory));
} }
else else
{ {
var subjectSelector = v2rayConfig.observatory.subjectSelector; var subjectSelector = _coreConfig.observatory.subjectSelector;
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []); subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
} }
} }
if (v2rayConfig.burstObservatory != null) if (_coreConfig.burstObservatory != null)
{ {
if (fullConfigTemplateNode["burstObservatory"] == null) if (fullConfigTemplateNode["burstObservatory"] == null)
{ {
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory)); fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory));
} }
else else
{ {
var subjectSelector = v2rayConfig.burstObservatory.subjectSelector; var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []); subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
} }
@ -88,7 +88,7 @@ public partial class CoreConfigV2rayService
var customOutboundsNode = new JsonArray(); var customOutboundsNode = new JsonArray();
foreach (var outbound in v2rayConfig.outbounds) foreach (var outbound in _coreConfig.outbounds)
{ {
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom") if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
{ {
@ -123,6 +123,6 @@ public partial class CoreConfigV2rayService
fullConfigTemplateNode["outbounds"] = customOutboundsNode; fullConfigTemplateNode["outbounds"] = customOutboundsNode;
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); return JsonUtils.Serialize(fullConfigTemplateNode);
} }
} }

View file

@ -2,46 +2,45 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenDns(ProfileItem? node, V2rayConfig v2rayConfig) private void GenDns()
{ {
try try
{ {
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); var item = context.RawDnsItem;
if (item is { Enabled: true }) if (item is { Enabled: true })
{ {
var result = await GenDnsCompatible(node, v2rayConfig); GenDnsCustom();
if (v2rayConfig.routing.domainStrategy != Global.IPIfNonMatch) if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch)
{ {
return result; return;
} }
// DNS routing // DNS routing
var dnsObj = JsonUtils.SerializeToNode(v2rayConfig.dns); var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns);
if (dnsObj == null) if (dnsObj == null)
{ {
return result; return;
} }
dnsObj["tag"] = Global.DnsTag; dnsObj["tag"] = Global.DnsTag;
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj)); _coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
v2rayConfig.routing.rules.Add(new RulesItem4Ray _coreConfig.routing.rules.Add(new RulesItem4Ray
{ {
type = "field", type = "field",
inboundTag = new List<string> { Global.DnsTag }, inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag, outboundTag = Global.ProxyTag,
}); });
return;
return result;
} }
var simpleDnsItem = _config.SimpleDNSItem; var simpleDnsItem = context.SimpleDnsItem;
var dnsItem = v2rayConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray(); var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray();
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs; var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
//Outbound Freedom domainStrategy //Outbound Freedom domainStrategy
if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs) if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs)
{ {
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null) if (outbound != null)
{ {
outbound.settings = new() outbound.settings = new()
@ -59,23 +58,23 @@ public partial class CoreConfigV2rayService
var xraySupportConfigTypeNames = Global.XraySupportConfigType var xraySupportConfigTypeNames = Global.XraySupportConfigType
.Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x]) .Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x])
.ToHashSet(); .ToHashSet();
v2rayConfig.outbounds _coreConfig.outbounds
.Where(t => xraySupportConfigTypeNames.Contains(t.protocol)) .Where(t => xraySupportConfigTypeNames.Contains(t.protocol))
.ToList() .ToList()
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy); .ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
} }
await GenDnsServers(node, dnsItem, simpleDnsItem); FillDnsServers(dnsItem);
await GenDnsHosts(dnsItem, simpleDnsItem); FillDnsHosts(dnsItem);
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null; dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null; dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) if (_coreConfig.routing.domainStrategy == Global.IPIfNonMatch)
{ {
// DNS routing // DNS routing
dnsItem.tag = Global.DnsTag; dnsItem.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray _coreConfig.routing.rules.Add(new RulesItem4Ray
{ {
type = "field", type = "field",
inboundTag = new List<string> { Global.DnsTag }, inboundTag = new List<string> { Global.DnsTag },
@ -83,17 +82,17 @@ public partial class CoreConfigV2rayService
}); });
} }
v2rayConfig.dns = dnsItem; _coreConfig.dns = dnsItem;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private async Task<int> GenDnsServers(ProfileItem? node, Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem) private void FillDnsServers(Dns4Ray dnsItem)
{ {
var simpleDNSItem = context.SimpleDnsItem;
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress) static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{ {
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';') var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
@ -197,9 +196,9 @@ public partial class CoreConfigV2rayService
} }
} }
var routing = await ConfigHandler.GetDefaultRouting(_config); var routing = context.RoutingItem;
List<RulesItem>? rules = null; List<RulesItem>? rules = null;
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? []; rules = JsonUtils.Deserialize<List<RulesItem>>(routing?.RuleSet) ?? [];
foreach (var item in rules) foreach (var item in rules)
{ {
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
@ -246,27 +245,9 @@ public partial class CoreConfigV2rayService
} }
} }
if (Utils.IsDomain(node?.Address)) if (context.ProtectDomainList.Count > 0)
{ {
directDomainList.Add(node.Address); directDomainList.AddRange(context.ProtectDomainList);
}
if (node?.Subid is not null)
{
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is not null)
{
foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile })
{
var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile);
if (profileNode is not null
&& Global.XraySupportConfigType.Contains(profileNode.ConfigType)
&& Utils.IsDomain(profileNode.Address))
{
directDomainList.Add(profileNode.Address);
}
}
}
} }
dnsItem.servers ??= []; dnsItem.servers ??= [];
@ -300,15 +281,14 @@ public partial class CoreConfigV2rayService
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
dnsItem.servers.AddRange(defaultDnsServers); dnsItem.servers.AddRange(defaultDnsServers);
return 0;
} }
private async Task<int> GenDnsHosts(Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem) private void FillDnsHosts(Dns4Ray dnsItem)
{ {
var simpleDNSItem = context.SimpleDnsItem;
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty()) if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
{ {
return await Task.FromResult(0); return;
} }
dnsItem.hosts ??= new Dictionary<string, object>(); dnsItem.hosts ??= new Dictionary<string, object>();
if (simpleDNSItem.AddCommonHosts == true) if (simpleDNSItem.AddCommonHosts == true)
@ -337,14 +317,13 @@ public partial class CoreConfigV2rayService
{ {
dnsItem.hosts[kvp.Key] = kvp.Value; dnsItem.hosts[kvp.Key] = kvp.Value;
} }
return await Task.FromResult(0);
} }
private async Task<int> GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig) private void GenDnsCustom()
{ {
try try
{ {
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); var item = context.RawDnsItem;
var normalDNS = item?.NormalDNS; var normalDNS = item?.NormalDNS;
var domainStrategy4Freedom = item?.DomainStrategy4Freedom; var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
if (normalDNS.IsNullOrEmpty()) if (normalDNS.IsNullOrEmpty())
@ -355,7 +334,7 @@ public partial class CoreConfigV2rayService
//Outbound Freedom domainStrategy //Outbound Freedom domainStrategy
if (domainStrategy4Freedom.IsNotEmpty()) if (domainStrategy4Freedom.IsNotEmpty())
{ {
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null) if (outbound != null)
{ {
outbound.settings = new(); outbound.settings = new();
@ -410,63 +389,37 @@ public partial class CoreConfigV2rayService
} }
} }
await GenDnsDomainsCompatible(node, obj, item); FillDnsDomainsCustom(obj);
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj)); _coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private async Task<int> GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dnsItem) private void FillDnsDomainsCustom(JsonNode dns)
{ {
if (node == null)
{
return 0;
}
var servers = dns["servers"]; var servers = dns["servers"];
if (servers != null) if (servers == null)
{ {
var domainList = new List<string>(); return;
if (Utils.IsDomain(node.Address))
{
domainList.Add(node.Address);
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is not null)
{
// Previous proxy
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)
&& Utils.IsDomain(prevNode.Address))
{
domainList.Add(prevNode.Address);
} }
// Next proxy var domainList = context.ProtectDomainList;
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (domainList.Count <= 0)
if (nextNode is not null
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
&& Utils.IsDomain(nextNode.Address))
{ {
domainList.Add(nextNode.Address); return;
} }
}
if (domainList.Count > 0) var dnsItem = context.RawDnsItem;
{
var dnsServer = new DnsServer4Ray() var dnsServer = new DnsServer4Ray()
{ {
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
skipFallback = true, skipFallback = true,
domains = domainList domains = domainList.ToList(),
}; };
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
} }
} }
return await Task.FromResult(0);
}
}

View file

@ -2,35 +2,35 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenInbounds(V2rayConfig v2rayConfig) private void GenInbounds()
{ {
try try
{ {
var listen = "0.0.0.0"; var listen = "0.0.0.0";
v2rayConfig.inbounds = []; _coreConfig.inbounds = [];
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true); var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
v2rayConfig.inbounds.Add(inbound); _coreConfig.inbounds.Add(inbound);
if (_config.Inbound.First().SecondLocalPortEnabled) if (_config.Inbound.First().SecondLocalPortEnabled)
{ {
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
v2rayConfig.inbounds.Add(inbound2); _coreConfig.inbounds.Add(inbound2);
} }
if (_config.Inbound.First().AllowLANConn) if (_config.Inbound.First().AllowLANConn)
{ {
if (_config.Inbound.First().NewPort4LAN) if (_config.Inbound.First().NewPort4LAN)
{ {
var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
inbound3.listen = listen; inbound3.listen = listen;
v2rayConfig.inbounds.Add(inbound3); _coreConfig.inbounds.Add(inbound3);
//auth //auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
{ {
inbound3.settings.auth = "password"; inbound3.settings.auth = "password";
inbound3.settings.accounts = new List<AccountsItem4Ray> { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; inbound3.settings.accounts = new List<AccountsItem4Ray> { new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
} }
} }
else else
@ -43,10 +43,9 @@ public partial class CoreConfigV2rayService
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return await Task.FromResult(0);
} }
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) private Inbounds4Ray BuildInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
{ {
var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound); var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
if (result.IsNullOrEmpty()) if (result.IsNullOrEmpty())

View file

@ -2,28 +2,27 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenLog(V2rayConfig v2rayConfig) private void GenLog()
{ {
try try
{ {
if (_config.CoreBasicItem.LogEnabled) if (_config.CoreBasicItem.LogEnabled)
{ {
var dtNow = DateTime.Now; var dtNow = DateTime.Now;
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); _coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); _coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
} }
else else
{ {
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
v2rayConfig.log.access = null; _coreConfig.log.access = null;
v2rayConfig.log.error = null; _coreConfig.log.error = null;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return await Task.FromResult(0);
} }
} }

View file

@ -2,13 +2,94 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenOutbound(ProfileItem node, Outbounds4Ray outbound) private void GenOutbounds()
{
var proxyOutboundList = BuildAllProxyOutbounds();
_coreConfig.outbounds.InsertRange(0, proxyOutboundList);
if (proxyOutboundList.Count(n => n.tag.StartsWith(Global.ProxyTag)) > 1)
{
var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad);
GenBalancer(multipleLoad);
}
}
private List<Outbounds4Ray> BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag)
{
var proxyOutboundList = new List<Outbounds4Ray>();
if (_node.ConfigType.IsGroupType())
{
proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName));
}
else
{
proxyOutboundList.Add(BuildProxyOutbound(baseTagName));
}
if (_config.CoreBasicItem.EnableFragment)
{
var fragmentOutbound = new Outbounds4Ray
{
protocol = "freedom",
tag = $"frag-{Global.ProxyTag}",
settings = new()
{
fragment = new()
{
packets = _config.Fragment4RayItem?.Packets,
length = _config.Fragment4RayItem?.Length,
interval = _config.Fragment4RayItem?.Interval
}
}
};
var actOutboundWithTlsList =
proxyOutboundList.Where(n => n.streamSettings?.security.IsNullOrEmpty() == false
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true));
foreach (var outbound in actOutboundWithTlsList)
{
var fragmentOutboundClone = JsonUtils.DeepCopy(fragmentOutbound);
fragmentOutboundClone.tag = $"frag-{outbound.tag}";
outbound.streamSettings.sockopt = new()
{
dialerProxy = fragmentOutboundClone.tag
};
proxyOutboundList.Add(fragmentOutboundClone);
}
}
return proxyOutboundList;
}
private List<Outbounds4Ray> BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag)
{
var proxyOutboundList = new List<Outbounds4Ray>();
switch (_node.ConfigType)
{
case EConfigType.PolicyGroup:
proxyOutboundList.AddRange(BuildOutboundsList(baseTagName));
break;
case EConfigType.ProxyChain:
proxyOutboundList.AddRange(BuildChainOutboundsList(baseTagName));
break;
}
return proxyOutboundList;
}
private Outbounds4Ray BuildProxyOutbound(string baseTagName = Global.ProxyTag)
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
FillOutbound(outbound);
outbound.tag = baseTagName;
return outbound;
}
private void FillOutbound(Outbounds4Ray outbound)
{ {
try try
{ {
var protocolExtra = node.GetProtocolExtra(); var protocolExtra = _node.GetProtocolExtra();
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
switch (node.ConfigType) switch (_node.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
{ {
@ -22,8 +103,8 @@ public partial class CoreConfigV2rayService
{ {
vnextItem = outbound.settings.vnext.First(); vnextItem = outbound.settings.vnext.First();
} }
vnextItem.address = node.Address; vnextItem.address = _node.Address;
vnextItem.port = node.Port; vnextItem.port = _node.Port;
UsersItem4Ray usersItem; UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0) if (vnextItem.users.Count <= 0)
@ -36,7 +117,7 @@ public partial class CoreConfigV2rayService
usersItem = vnextItem.users.First(); usersItem = vnextItem.users.First();
} }
usersItem.id = node.Password; usersItem.id = _node.Password;
usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0;
usersItem.email = Global.UserEMail; usersItem.email = Global.UserEMail;
if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity))
@ -48,7 +129,7 @@ public partial class CoreConfigV2rayService
usersItem.security = Global.DefaultSecurity; usersItem.security = Global.DefaultSecurity;
} }
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); FillOutboundMux(outbound, muxEnabled, muxEnabled);
outbound.settings.servers = null; outbound.settings.servers = null;
break; break;
@ -65,16 +146,16 @@ public partial class CoreConfigV2rayService
{ {
serversItem = outbound.settings.servers.First(); serversItem = outbound.settings.servers.First();
} }
serversItem.address = node.Address; serversItem.address = _node.Address;
serversItem.port = node.Port; serversItem.port = _node.Port;
serversItem.password = node.Password; serversItem.password = _node.Password;
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) serversItem.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod)
? protocolExtra.SsMethod : "none"; ? protocolExtra.SsMethod : "none";
serversItem.ota = false; serversItem.ota = false;
serversItem.level = 1; serversItem.level = 1;
await GenOutboundMux(node, outbound); FillOutboundMux(outbound);
outbound.settings.vnext = null; outbound.settings.vnext = null;
break; break;
@ -92,25 +173,25 @@ public partial class CoreConfigV2rayService
{ {
serversItem = outbound.settings.servers.First(); serversItem = outbound.settings.servers.First();
} }
serversItem.address = node.Address; serversItem.address = _node.Address;
serversItem.port = node.Port; serversItem.port = _node.Port;
serversItem.method = null; serversItem.method = null;
serversItem.password = null; serversItem.password = null;
if (node.Username.IsNotEmpty() if (_node.Username.IsNotEmpty()
&& node.Password.IsNotEmpty()) && _node.Password.IsNotEmpty())
{ {
SocksUsersItem4Ray socksUsersItem = new() SocksUsersItem4Ray socksUsersItem = new()
{ {
user = node.Username ?? "", user = _node.Username ?? "",
pass = node.Password, pass = _node.Password,
level = 1 level = 1
}; };
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem }; serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem };
} }
await GenOutboundMux(node, outbound); FillOutboundMux(outbound);
outbound.settings.vnext = null; outbound.settings.vnext = null;
break; break;
@ -127,8 +208,8 @@ public partial class CoreConfigV2rayService
{ {
vnextItem = outbound.settings.vnext.First(); vnextItem = outbound.settings.vnext.First();
} }
vnextItem.address = node.Address; vnextItem.address = _node.Address;
vnextItem.port = node.Port; vnextItem.port = _node.Port;
UsersItem4Ray usersItem; UsersItem4Ray usersItem;
if (vnextItem.users.Count <= 0) if (vnextItem.users.Count <= 0)
@ -140,7 +221,7 @@ public partial class CoreConfigV2rayService
{ {
usersItem = vnextItem.users.First(); usersItem = vnextItem.users.First();
} }
usersItem.id = node.Password; usersItem.id = _node.Password;
usersItem.email = Global.UserEMail; usersItem.email = Global.UserEMail;
usersItem.encryption = protocolExtra.VlessEncryption; usersItem.encryption = protocolExtra.VlessEncryption;
@ -150,7 +231,7 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
await GenOutboundMux(node, outbound, false, muxEnabled); FillOutboundMux(outbound, false, muxEnabled);
} }
outbound.settings.servers = null; outbound.settings.servers = null;
break; break;
@ -167,14 +248,14 @@ public partial class CoreConfigV2rayService
{ {
serversItem = outbound.settings.servers.First(); serversItem = outbound.settings.servers.First();
} }
serversItem.address = node.Address; serversItem.address = _node.Address;
serversItem.port = node.Port; serversItem.port = _node.Port;
serversItem.password = node.Password; serversItem.password = _node.Password;
serversItem.ota = false; serversItem.ota = false;
serversItem.level = 1; serversItem.level = 1;
await GenOutboundMux(node, outbound); FillOutboundMux(outbound);
outbound.settings.vnext = null; outbound.settings.vnext = null;
break; break;
@ -184,8 +265,8 @@ public partial class CoreConfigV2rayService
outbound.settings = new() outbound.settings = new()
{ {
version = 2, version = 2,
address = node.Address, address = _node.Address,
port = node.Port, port = _node.Port,
}; };
outbound.settings.vnext = null; outbound.settings.vnext = null;
outbound.settings.servers = null; outbound.settings.servers = null;
@ -193,7 +274,7 @@ public partial class CoreConfigV2rayService
} }
case EConfigType.WireGuard: case EConfigType.WireGuard:
{ {
var address = node.Address; var address = _node.Address;
if (Utils.IsIpv6(address)) if (Utils.IsIpv6(address))
{ {
address = $"[{address}]"; address = $"[{address}]";
@ -201,12 +282,12 @@ public partial class CoreConfigV2rayService
var peer = new WireguardPeer4Ray var peer = new WireguardPeer4Ray
{ {
publicKey = protocolExtra.WgPublicKey ?? "", publicKey = protocolExtra.WgPublicKey ?? "",
endpoint = address + ":" + node.Port.ToString() endpoint = address + ":" + _node.Port.ToString()
}; };
var setting = new Outboundsettings4Ray var setting = new Outboundsettings4Ray
{ {
address = Utils.String2List(protocolExtra.WgInterfaceAddress), address = Utils.String2List(protocolExtra.WgInterfaceAddress),
secretKey = node.Password, secretKey = _node.Password,
reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(),
mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(), mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(),
peers = [peer] peers = [peer]
@ -218,21 +299,20 @@ public partial class CoreConfigV2rayService
} }
} }
outbound.protocol = Global.ProtocolTypes[node.ConfigType]; outbound.protocol = Global.ProtocolTypes[_node.ConfigType];
if (node.ConfigType == EConfigType.Hysteria2) if (_node.ConfigType == EConfigType.Hysteria2)
{ {
outbound.protocol = "hysteria"; outbound.protocol = "hysteria";
} }
await GenBoundStreamSettings(node, outbound); FillBoundStreamSettings(outbound);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private async Task<int> GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) private void FillOutboundMux(Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false)
{ {
try try
{ {
@ -255,23 +335,22 @@ public partial class CoreConfigV2rayService
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return await Task.FromResult(0);
} }
private async Task<int> GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound) private void FillBoundStreamSettings(Outbounds4Ray outbound)
{ {
try try
{ {
var streamSettings = outbound.streamSettings; var streamSettings = outbound.streamSettings;
var network = node.GetNetwork(); var network = _node.GetNetwork();
if (node.ConfigType == EConfigType.Hysteria2) if (_node.ConfigType == EConfigType.Hysteria2)
{ {
network = "hysteria"; network = "hysteria";
} }
streamSettings.network = network; streamSettings.network = network;
var host = node.RequestHost.TrimEx(); var host = _node.RequestHost.TrimEx();
var path = node.Path.TrimEx(); var path = _node.Path.TrimEx();
var sni = node.Sni.TrimEx(); var sni = _node.Sni.TrimEx();
var useragent = ""; var useragent = "";
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty()) if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
{ {
@ -286,17 +365,17 @@ public partial class CoreConfigV2rayService
} }
//if tls //if tls
if (node.StreamSecurity == Global.StreamSecurity) if (_node.StreamSecurity == Global.StreamSecurity)
{ {
streamSettings.security = node.StreamSecurity; streamSettings.security = _node.StreamSecurity;
TlsSettings4Ray tlsSettings = new() TlsSettings4Ray tlsSettings = new()
{ {
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), allowInsecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure),
alpn = node.GetAlpn(), alpn = _node.GetAlpn(),
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
echConfigList = node.EchConfigList.NullIfEmpty(), echConfigList = _node.EchConfigList.NullIfEmpty(),
echForceQuery = node.EchForceQuery.NullIfEmpty() echForceQuery = _node.EchForceQuery.NullIfEmpty()
}; };
if (sni.IsNotEmpty()) if (sni.IsNotEmpty())
{ {
@ -306,7 +385,7 @@ public partial class CoreConfigV2rayService
{ {
tlsSettings.serverName = Utils.String2List(host)?.First(); tlsSettings.serverName = Utils.String2List(host)?.First();
} }
var certs = CertPemManager.ParsePemChain(node.Cert); var certs = CertPemManager.ParsePemChain(_node.Cert);
if (certs.Count > 0) if (certs.Count > 0)
{ {
var certsettings = new List<CertificateSettings4Ray>(); var certsettings = new List<CertificateSettings4Ray>();
@ -323,27 +402,27 @@ public partial class CoreConfigV2rayService
tlsSettings.disableSystemRoot = true; tlsSettings.disableSystemRoot = true;
tlsSettings.allowInsecure = false; tlsSettings.allowInsecure = false;
} }
else if (!node.CertSha.IsNullOrEmpty()) else if (!_node.CertSha.IsNullOrEmpty())
{ {
tlsSettings.pinnedPeerCertSha256 = node.CertSha; tlsSettings.pinnedPeerCertSha256 = _node.CertSha;
tlsSettings.allowInsecure = false; tlsSettings.allowInsecure = false;
} }
streamSettings.tlsSettings = tlsSettings; streamSettings.tlsSettings = tlsSettings;
} }
//if Reality //if Reality
if (node.StreamSecurity == Global.StreamSecurityReality) if (_node.StreamSecurity == Global.StreamSecurityReality)
{ {
streamSettings.security = node.StreamSecurity; streamSettings.security = _node.StreamSecurity;
TlsSettings4Ray realitySettings = new() TlsSettings4Ray realitySettings = new()
{ {
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
serverName = sni, serverName = sni,
publicKey = node.PublicKey, publicKey = _node.PublicKey,
shortId = node.ShortId, shortId = _node.ShortId,
spiderX = node.SpiderX, spiderX = _node.SpiderX,
mldsa65Verify = node.Mldsa65Verify, mldsa65Verify = _node.Mldsa65Verify,
show = false, show = false,
}; };
@ -367,14 +446,14 @@ public partial class CoreConfigV2rayService
kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize; kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize;
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize; kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
streamSettings.finalmask ??= new(); streamSettings.finalmask ??= new();
if (Global.KcpHeaderMaskMap.TryGetValue(node.HeaderType, out var header)) if (Global.KcpHeaderMaskMap.TryGetValue(_node.HeaderType, out var header))
{ {
streamSettings.finalmask.udp = streamSettings.finalmask.udp =
[ [
new Mask4Ray new Mask4Ray
{ {
type = header, type = header,
settings = node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null settings = _node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null
} }
]; ];
} }
@ -444,17 +523,17 @@ public partial class CoreConfigV2rayService
{ {
xhttpSettings.host = host; xhttpSettings.host = host;
} }
if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType)) if (_node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(_node.HeaderType))
{ {
xhttpSettings.mode = node.HeaderType; xhttpSettings.mode = _node.HeaderType;
} }
if (node.Extra.IsNotEmpty()) if (_node.Extra.IsNotEmpty())
{ {
xhttpSettings.extra = JsonUtils.ParseJson(node.Extra); xhttpSettings.extra = JsonUtils.ParseJson(_node.Extra);
} }
streamSettings.xhttpSettings = xhttpSettings; streamSettings.xhttpSettings = xhttpSettings;
await GenOutboundMux(node, outbound); FillOutboundMux(outbound);
break; break;
//h2 //h2
@ -478,11 +557,11 @@ public partial class CoreConfigV2rayService
key = path, key = path,
header = new Header4Ray header = new Header4Ray
{ {
type = node.HeaderType type = _node.HeaderType
} }
}; };
streamSettings.quicSettings = quicsettings; streamSettings.quicSettings = quicsettings;
if (node.StreamSecurity == Global.StreamSecurity) if (_node.StreamSecurity == Global.StreamSecurity)
{ {
if (sni.IsNotEmpty()) if (sni.IsNotEmpty())
{ {
@ -490,7 +569,7 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
streamSettings.tlsSettings.serverName = node.Address; streamSettings.tlsSettings.serverName = _node.Address;
} }
} }
break; break;
@ -500,7 +579,7 @@ public partial class CoreConfigV2rayService
{ {
authority = host.NullIfEmpty(), authority = host.NullIfEmpty(),
serviceName = path, serviceName = path,
multiMode = node.HeaderType == Global.GrpcMultiMode, multiMode = _node.HeaderType == Global.GrpcMultiMode,
idle_timeout = _config.GrpcItem.IdleTimeout, idle_timeout = _config.GrpcItem.IdleTimeout,
health_check_timeout = _config.GrpcItem.HealthCheckTimeout, health_check_timeout = _config.GrpcItem.HealthCheckTimeout,
permit_without_stream = _config.GrpcItem.PermitWithoutStream, permit_without_stream = _config.GrpcItem.PermitWithoutStream,
@ -510,7 +589,7 @@ public partial class CoreConfigV2rayService
break; break;
case "hysteria": case "hysteria":
var protocolExtra = node.GetProtocolExtra(); var protocolExtra = _node.GetProtocolExtra();
var ports = protocolExtra?.Ports; var ports = protocolExtra?.Ports;
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0 int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
? su ? su
@ -536,7 +615,7 @@ public partial class CoreConfigV2rayService
streamSettings.hysteriaSettings = new() streamSettings.hysteriaSettings = new()
{ {
version = 2, version = 2,
auth = node.Password, auth = _node.Password,
up = upMbps > 0 ? $"{upMbps}mbps" : null, up = upMbps > 0 ? $"{upMbps}mbps" : null,
down = downMbps > 0 ? $"{downMbps}mbps" : null, down = downMbps > 0 ? $"{downMbps}mbps" : null,
udphop = udpHop, udphop = udpHop,
@ -557,13 +636,13 @@ public partial class CoreConfigV2rayService
default: default:
//tcp //tcp
if (node.HeaderType == Global.TcpHeaderHttp) if (_node.HeaderType == Global.TcpHeaderHttp)
{ {
TcpSettings4Ray tcpSettings = new() TcpSettings4Ray tcpSettings = new()
{ {
header = new Header4Ray header = new Header4Ray
{ {
type = node.HeaderType type = _node.HeaderType
} }
}; };
@ -592,410 +671,132 @@ public partial class CoreConfigV2rayService
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private async Task<int> GenGroupOutbound(ProfileItem node, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false) private List<Outbounds4Ray> BuildOutboundsList(string baseTagName = Global.ProxyTag)
{ {
try var nodes = new List<ProfileItem>();
foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? [])
{ {
if (!node.ConfigType.IsGroupType()) if (context.AllProxiesMap.TryGetValue(nodeId, out var node))
{ {
return -1; nodes.Add(node);
}
var hasCycle = await GroupProfileManager.HasCycle(node);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, v2rayConfig, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, v2rayConfig, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, v2rayConfig, baseTagName);
break;
default:
break;
}
//add balancers
if (node.ConfigType == EConfigType.PolicyGroup)
{
var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing;
await GenObservatory(v2rayConfig, multipleLoad, baseTagName);
await GenBalancer(v2rayConfig, multipleLoad, baseTagName);
} }
} }
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig)
{
//fragment proxy
if (_config.CoreBasicItem.EnableFragment
&& v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false)
{
var fragmentOutbound = new Outbounds4Ray
{
protocol = "freedom",
tag = $"frag-{Global.ProxyTag}",
settings = new()
{
fragment = new()
{
packets = _config.Fragment4RayItem?.Packets,
length = _config.Fragment4RayItem?.Length,
interval = _config.Fragment4RayItem?.Interval
}
}
};
v2rayConfig.outbounds.Add(fragmentOutbound);
v2rayConfig.outbounds.First().streamSettings.sockopt = new()
{
dialerProxy = fragmentOutbound.tag
};
return 0;
}
if (node.Subid.IsNullOrEmpty())
{
return 0;
}
try
{
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is null)
{
return 0;
}
//current proxy
var outbound = v2rayConfig.outbounds.First();
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
//Previous proxy
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null;
if (prevNode is not null
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevOutboundTag = $"prev-{Global.ProxyTag}";
prevOutbound.tag = prevOutboundTag;
v2rayConfig.outbounds.Add(prevOutbound);
}
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
if (nextOutbound is not null)
{
v2rayConfig.outbounds.Insert(0, nextOutbound);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenOutboundsListWithChain(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
{
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
// Cache for chain proxies to avoid duplicate generation
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
var prevIndex = 0; // Index for prev outbounds
// Process nodes
var index = 0;
foreach (var node in nodes)
{
index++;
if (node.ConfigType.IsGroupType())
{
var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
continue;
}
var childBaseTagName = $"{baseTagName}-{index}";
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
continue;
}
// Handle proxy chain
string? prevTag = null;
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
if (nextOutbound != null)
{
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
// current proxy
await GenOutbound(node, currentOutbound);
currentOutbound.tag = $"{baseTagName}-{index}";
if (!node.Subid.IsNullOrEmpty())
{
if (prevProxyTags.TryGetValue(node.Subid, out var value))
{
prevTag = value; // maybe null
}
else
{
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevTag = $"prev-{baseTagName}-{++prevIndex}";
prevOutbound.tag = prevTag;
prevOutbounds.Add(prevOutbound);
}
prevProxyTags[node.Subid] = prevTag;
}
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
if (!nextProxyCache.ContainsKey(node.Subid))
{
nextProxyCache[node.Subid] = nextOutbound;
}
}
if (nextOutbound is not null)
{
resultOutbounds.Add(nextOutbound);
}
resultOutbounds.Add(currentOutbound);
}
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
if (baseTagName == Global.ProxyTag)
{
resultOutbounds.AddRange(prevOutbounds);
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = resultOutbounds;
}
else
{
v2rayConfig.outbounds.AddRange(prevOutbounds);
v2rayConfig.outbounds.AddRange(resultOutbounds);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
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 AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null
&& Global.XraySupportConfigType.Contains(nextNode.ConfigType))
{
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> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
{
var resultOutbounds = new List<Outbounds4Ray>(); var resultOutbounds = new List<Outbounds4Ray>();
for (var i = 0; i < nodes.Count; i++) for (var i = 0; i < nodes.Count; i++)
{ {
var node = nodes[i]; var node = nodes[i];
if (node == null) var currentTag = $"{baseTagName}-{i + 1}";
{
continue;
}
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node); var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag);
if (childProfiles.Count <= 0) resultOutbounds.AddRange(childProfiles);
{
continue; continue;
} }
var childBaseTagName = $"{baseTagName}-{i + 1}"; var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound();
var ret = node.ConfigType switch outbound.tag = currentTag;
{
EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
continue;
}
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (txtOutbound.IsNullOrEmpty())
{
break;
}
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
var result = await GenOutbound(node, outbound);
if (result != 0)
{
break;
}
outbound.tag = baseTagName + (i + 1).ToString();
resultOutbounds.Add(outbound); resultOutbounds.Add(outbound);
} }
if (baseTagName == Global.ProxyTag) return resultOutbounds;
{
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = resultOutbounds;
}
else
{
v2rayConfig.outbounds.AddRange(resultOutbounds);
}
return await Task.FromResult(0);
} }
private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) private List<Outbounds4Ray> BuildChainOutboundsList(string baseTagName = Global.ProxyTag)
{ {
var nodes = new List<ProfileItem>();
foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? [])
{
if (context.AllProxiesMap.TryGetValue(nodeId, out var node))
{
nodes.Add(node);
}
}
// Based on actual network flow instead of data packets // Based on actual network flow instead of data packets
var nodesReverse = nodes.AsEnumerable().Reverse().ToList(); var nodesReverse = nodes.AsEnumerable().Reverse().ToList();
var resultOutbounds = new List<Outbounds4Ray>(); var resultOutbounds = new List<Outbounds4Ray>();
for (var i = 0; i < nodesReverse.Count; i++) for (var i = 0; i < nodesReverse.Count; i++)
{ {
var node = nodesReverse[i]; var node = nodesReverse[i];
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}";
if (txtOutbound.IsNullOrEmpty()) var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null;
if (node.ConfigType.IsGroupType())
{ {
break; var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag);
if (!dialerProxyTag.IsNullOrEmpty())
{
var chainEndNodes =
childProfiles.Where(n => n?.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true);
foreach (var chainEndNode in chainEndNodes)
{
chainEndNode.streamSettings.sockopt = new()
{
dialerProxy = dialerProxyTag
};
} }
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); }
var result = await GenOutbound(node, outbound); if (i != 0)
{
var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList();
if (chainStartNodes.Count == 1)
{
foreach (var existedChainEndNode in resultOutbounds.Where(n => n.streamSettings?.sockopt?.dialerProxy == currentTag))
{
existedChainEndNode.streamSettings.sockopt = new()
{
dialerProxy = chainStartNodes.First().tag
};
}
}
else if (chainStartNodes.Count > 1)
{
var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds);
resultOutbounds.Clear();
var j = 0;
foreach (var chainStartNode in chainStartNodes)
{
var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes);
foreach (var existedChainNode in existedChainNodesClone)
{
var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}";
existedChainNode.tag = cloneTag;
}
for (var k = 0; k < existedChainNodesClone.Count; k++)
{
var existedChainNode = existedChainNodesClone[k];
var previousDialerProxyTag = existedChainNode.streamSettings?.sockopt?.dialerProxy;
var nextTag = k + 1 < existedChainNodesClone.Count
? existedChainNodesClone[k + 1].tag
: chainStartNode.tag;
existedChainNode.streamSettings.sockopt = new()
{
dialerProxy = (previousDialerProxyTag == currentTag)
? chainStartNode.tag
: nextTag
};
resultOutbounds.Add(existedChainNode);
}
j++;
}
}
}
resultOutbounds.AddRange(childProfiles);
continue;
}
var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound();
if (result != 0) outbound.tag = currentTag;
{
break;
}
if (i == 0) if (!dialerProxyTag.IsNullOrEmpty())
{
outbound.tag = baseTagName;
}
else
{
// avoid v2ray observe
outbound.tag = "chain-" + baseTagName + i.ToString();
}
if (i != nodesReverse.Count - 1)
{ {
outbound.streamSettings.sockopt = new() outbound.streamSettings.sockopt = new()
{ {
dialerProxy = "chain-" + baseTagName + (i + 1).ToString() dialerProxy = dialerProxyTag
}; };
} }
resultOutbounds.Add(outbound); resultOutbounds.Add(outbound);
} }
if (baseTagName == Global.ProxyTag) return resultOutbounds;
{
resultOutbounds.AddRange(v2rayConfig.outbounds);
v2rayConfig.outbounds = resultOutbounds;
}
else
{
v2rayConfig.outbounds.AddRange(resultOutbounds);
}
return await Task.FromResult(0);
} }
} }

View file

@ -2,20 +2,20 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenRouting(V2rayConfig v2rayConfig) private void GenRouting()
{ {
try try
{ {
if (v2rayConfig.routing?.rules != null) if (_coreConfig.routing?.rules != null)
{ {
v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; _coreConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
var routing = await ConfigHandler.GetDefaultRouting(_config); var routing = context.RoutingItem;
if (routing != null) if (routing != null)
{ {
if (routing.DomainStrategy.IsNotEmpty()) if (routing.DomainStrategy.IsNotEmpty())
{ {
v2rayConfig.routing.domainStrategy = routing.DomainStrategy; _coreConfig.routing.domainStrategy = routing.DomainStrategy;
} }
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item in rules) foreach (var item in rules)
@ -31,7 +31,18 @@ public partial class CoreConfigV2rayService
} }
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item)); var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
await GenRoutingUserRule(item2, v2rayConfig); GenRoutingUserRule(item2);
}
}
var balancerTagList = _coreConfig.routing.balancers
?.Select(p => p.tag)
.ToList() ?? [];
if (balancerTagList.Count > 0)
{
foreach (var rulesItem in _coreConfig.routing.rules.Where(r => balancerTagList.Contains(r.outboundTag)))
{
rulesItem.balancerTag = rulesItem.outboundTag;
rulesItem.outboundTag = null;
} }
} }
} }
@ -40,95 +51,94 @@ public partial class CoreConfigV2rayService
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return 0;
} }
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig) private void GenRoutingUserRule(RulesItem4Ray? userRule)
{ {
try try
{ {
if (rule == null) if (userRule == null)
{ {
return 0; return;
} }
rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig); userRule.outboundTag = GenRoutingUserRuleOutbound(userRule.outboundTag ?? Global.ProxyTag);
if (rule.port.IsNullOrEmpty()) if (userRule.port.IsNullOrEmpty())
{ {
rule.port = null; userRule.port = null;
} }
if (rule.network.IsNullOrEmpty()) if (userRule.network.IsNullOrEmpty())
{ {
rule.network = null; userRule.network = null;
} }
if (rule.domain?.Count == 0) if (userRule.domain?.Count == 0)
{ {
rule.domain = null; userRule.domain = null;
} }
if (rule.ip?.Count == 0) if (userRule.ip?.Count == 0)
{ {
rule.ip = null; userRule.ip = null;
} }
if (rule.protocol?.Count == 0) if (userRule.protocol?.Count == 0)
{ {
rule.protocol = null; userRule.protocol = null;
} }
if (rule.inboundTag?.Count == 0) if (userRule.inboundTag?.Count == 0)
{ {
rule.inboundTag = null; userRule.inboundTag = null;
} }
if (rule.process?.Count == 0) if (userRule.process?.Count == 0)
{ {
rule.process = null; userRule.process = null;
} }
var hasDomainIp = false; var hasDomainIp = false;
if (rule.domain?.Count > 0) if (userRule.domain?.Count > 0)
{ {
var it = JsonUtils.DeepCopy(rule); var it = JsonUtils.DeepCopy(userRule);
it.ip = null; it.ip = null;
it.process = null; it.process = null;
it.type = "field"; it.type = "field";
for (var k = it.domain.Count - 1; k >= 0; k--) for (var k = it.domain.Count - 1; k >= 0; k--)
{ {
if (it.domain[k].StartsWith("#")) if (it.domain[k].StartsWith('#'))
{ {
it.domain.RemoveAt(k); it.domain.RemoveAt(k);
} }
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ","); it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
} }
v2rayConfig.routing.rules.Add(it); _coreConfig.routing.rules.Add(it);
hasDomainIp = true; hasDomainIp = true;
} }
if (rule.ip?.Count > 0) if (userRule.ip?.Count > 0)
{ {
var it = JsonUtils.DeepCopy(rule); var it = JsonUtils.DeepCopy(userRule);
it.domain = null; it.domain = null;
it.process = null; it.process = null;
it.type = "field"; it.type = "field";
v2rayConfig.routing.rules.Add(it); _coreConfig.routing.rules.Add(it);
hasDomainIp = true; hasDomainIp = true;
} }
if (rule.process?.Count > 0) if (userRule.process?.Count > 0)
{ {
var it = JsonUtils.DeepCopy(rule); var it = JsonUtils.DeepCopy(userRule);
it.domain = null; it.domain = null;
it.ip = null; it.ip = null;
it.type = "field"; it.type = "field";
v2rayConfig.routing.rules.Add(it); _coreConfig.routing.rules.Add(it);
hasDomainIp = true; hasDomainIp = true;
} }
if (!hasDomainIp) if (!hasDomainIp)
{ {
if (rule.port.IsNotEmpty() if (userRule.port.IsNotEmpty()
|| rule.protocol?.Count > 0 || userRule.protocol?.Count > 0
|| rule.inboundTag?.Count > 0 || userRule.inboundTag?.Count > 0
|| rule.network != null || userRule.network != null
) )
{ {
var it = JsonUtils.DeepCopy(rule); var it = JsonUtils.DeepCopy(userRule);
it.type = "field"; it.type = "field";
v2rayConfig.routing.rules.Add(it); _coreConfig.routing.rules.Add(it);
} }
} }
} }
@ -136,17 +146,16 @@ public partial class CoreConfigV2rayService
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
} }
return await Task.FromResult(0);
} }
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig) private string GenRoutingUserRuleOutbound(string outboundTag)
{ {
if (Global.OutboundTags.Contains(outboundTag)) if (Global.OutboundTags.Contains(outboundTag))
{ {
return outboundTag; return outboundTag;
} }
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
if (node == null if (node == null
|| (!Global.XraySupportConfigType.Contains(node.ConfigType) || (!Global.XraySupportConfigType.Contains(node.ConfigType)
@ -156,27 +165,20 @@ public partial class CoreConfigV2rayService
} }
var tag = $"{node.IndexId}-{Global.ProxyTag}"; var tag = $"{node.IndexId}-{Global.ProxyTag}";
if (v2rayConfig.outbounds.Any(p => p.tag == tag)) if (_coreConfig.outbounds.Any(p => p.tag.StartsWith(tag)))
{ {
return tag; return tag;
} }
if (node.ConfigType.IsGroupType()) var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
{ _coreConfig.outbounds.AddRange(proxyOutbounds);
var ret = await GenGroupOutbound(node, v2rayConfig, tag); if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
if (ret == 0)
{ {
var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad, tag);
GenBalancer(multipleLoad, tag);
}
return tag; return tag;
} }
return Global.ProxyTag;
}
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(node, outbound);
outbound.tag = tag;
v2rayConfig.outbounds.Add(outbound);
return outbound.tag;
}
} }

View file

@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenStatistic(V2rayConfig v2rayConfig) private void GenStatistic()
{ {
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{ {
@ -11,17 +11,17 @@ public partial class CoreConfigV2rayService
Policy4Ray policyObj = new(); Policy4Ray policyObj = new();
SystemPolicy4Ray policySystemSetting = new(); SystemPolicy4Ray policySystemSetting = new();
v2rayConfig.stats = new Stats4Ray(); _coreConfig.stats = new Stats4Ray();
apiObj.tag = tag; apiObj.tag = tag;
v2rayConfig.metrics = apiObj; _coreConfig.metrics = apiObj;
policySystemSetting.statsOutboundDownlink = true; policySystemSetting.statsOutboundDownlink = true;
policySystemSetting.statsOutboundUplink = true; policySystemSetting.statsOutboundUplink = true;
policyObj.system = policySystemSetting; policyObj.system = policySystemSetting;
v2rayConfig.policy = policyObj; _coreConfig.policy = policyObj;
if (!v2rayConfig.inbounds.Exists(item => item.tag == tag)) if (!_coreConfig.inbounds.Exists(item => item.tag == tag))
{ {
Inbounds4Ray apiInbound = new(); Inbounds4Ray apiInbound = new();
Inboundsettings4Ray apiInboundSettings = new(); Inboundsettings4Ray apiInboundSettings = new();
@ -31,10 +31,10 @@ public partial class CoreConfigV2rayService
apiInbound.protocol = Global.InboundAPIProtocol; apiInbound.protocol = Global.InboundAPIProtocol;
apiInboundSettings.address = Global.Loopback; apiInboundSettings.address = Global.Loopback;
apiInbound.settings = apiInboundSettings; apiInbound.settings = apiInboundSettings;
v2rayConfig.inbounds.Add(apiInbound); _coreConfig.inbounds.Add(apiInbound);
} }
if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag)) if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag))
{ {
RulesItem4Ray apiRoutingRule = new() RulesItem4Ray apiRoutingRule = new()
{ {
@ -43,9 +43,8 @@ public partial class CoreConfigV2rayService
type = "field" type = "field"
}; };
v2rayConfig.routing.rules.Add(apiRoutingRule); _coreConfig.routing.rules.Add(apiRoutingRule);
} }
} }
return await Task.FromResult(0);
} }
} }

View file

@ -801,7 +801,8 @@ public class ProfilesViewModel : MyReactiveObject
if (blClipboard) if (blClipboard)
{ {
var result = await CoreConfigHandler.GenerateClientConfig(item, null); var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item);
var result = await CoreConfigHandler.GenerateClientConfig(context, null);
if (result.Success != true) if (result.Success != true)
{ {
NoticeManager.Instance.Enqueue(result.Msg); NoticeManager.Instance.Enqueue(result.Msg);
@ -824,7 +825,8 @@ public class ProfilesViewModel : MyReactiveObject
{ {
return; return;
} }
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item);
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true) if (result.Success != true)
{ {
NoticeManager.Instance.Enqueue(result.Msg); NoticeManager.Instance.Enqueue(result.Msg);