Support sing-box 1.14 (#9736)

* Support sing-box 1.14

* Unified format

* Support parallel dns

* Add `optimistic DNS caching` support

* Try fix serial multi DNS

* Add HttpClient support
This commit is contained in:
DHR60
2026-09-02 03:54:06 +00:00
committed by GitHub
parent 1bf3d14771
commit f62eaab8c5
9 changed files with 337 additions and 151 deletions

View File

@@ -378,50 +378,6 @@ public class CoreConfigSingboxServiceTests
await hasRouteRule.Should().BeTrue();
}
[Test]
public async Task GenerateClientConfigContent_DirectExpectedIPs_ShouldApplyGeoipAndCidrToDirectDnsRule()
{
var config = CoreConfigTestFactory.CreateConfigWithDirectExpectedIPs(
ECoreType.sing_box,
"192.168.0.0/16,geoip:cn");
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateSocksNode(ECoreType.sing_box, "n-main", "main");
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
{
RoutingItem = new RoutingItem
{
Id = "r-dns-direct-expected",
Remarks = "dns-direct-expected",
RuleSet = JsonUtils.Serialize(new List<RulesItem>
{
new()
{
Enabled = true,
RuleType = ERuleType.DNS,
OutboundTag = Global.DirectTag,
Domain = ["geosite:cn"],
}
}),
DomainStrategy = Global.AsIs,
DomainStrategy4Singbox = string.Empty,
}
};
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
var hasExpectedRule = cfg.dns.rules?.Any(r =>
r.server == Global.SingboxDirectDNSTag
&& r.ip_cidr?.Contains("192.168.0.0/16") == true
&& r.rule_set?.Contains("geosite-cn") == true
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
await hasExpectedRule.Should().BeTrue();
}
[Test]
public async Task GenerateClientConfigContent_BootstrapDNS_ShouldConfigurePureIPResolver()
{
@@ -544,14 +500,18 @@ public class CoreConfigSingboxServiceTests
Remarks = "dns-direct-variant",
RuleSet = JsonUtils.Serialize(new List<RulesItem>
{
new()
{
Enabled = true, RuleType = ERuleType.DNS, OutboundTag = Global.ProxyTag, Domain = ["geosite:google"],
},
new()
{
Enabled = true, RuleType = ERuleType.DNS, OutboundTag = Global.DirectTag, Domain = [domainTag],
}
},
}),
DomainStrategy = Global.AsIs,
DomainStrategy4Singbox = string.Empty,
}
},
};
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
@@ -559,11 +519,14 @@ public class CoreConfigSingboxServiceTests
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
var hasExpectedRule = cfg.dns.rules?.Any(r =>
r.server == Global.SingboxDirectDNSTag
&& r.ip_cidr?.Contains("192.168.0.0/16") == true
&& r.rule_set?.Contains(expectedRuleSetTag) == true
&& r.rule_set?.Contains("geoip-cn") == true) ?? false;
var hasExpectedRule = (cfg.dns.rules?.Any(r =>
r.ip_cidr?.Contains("192.168.0.0/16") == true
&& r.action == "respond"
&& r.rule_set?.Contains("geoip-cn") == true) ?? false)
&& (cfg.dns.rules?.Any(r =>
r.server == Global.SingboxDirectDNSTag
&& r.rule_set?.Contains(expectedRuleSetTag) == true
&& r.action == "evaluate") ?? false);
await hasExpectedRule.Should().BeTrue();
}
@@ -632,6 +595,60 @@ public class CoreConfigSingboxServiceTests
await cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
}
[Test]
[Arguments(true)]
[Arguments(false)]
public async Task GenerateClientConfigContent_MultiDnsServers_ShouldIncludeAllServers(bool parallelQuery)
{
var config =
CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
config.SimpleDNSItem.ParallelQuery = parallelQuery;
config.SimpleDNSItem.DirectDNS = "1.1.1.1,2.2.2.2";
config.SimpleDNSItem.RemoteDNS = "3.3.3.3,4.4.4.4";
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateSocksNode(ECoreType.sing_box, "n-main", "main");
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
{
RoutingItem = new RoutingItem
{
Id = "r-dns-direct-variant",
Remarks = "dns-direct-variant",
RuleSet = JsonUtils.Serialize(new List<RulesItem>
{
new()
{
Enabled = true,
RuleType = ERuleType.DNS,
OutboundTag = Global.ProxyTag,
Domain = ["geosite:google"],
},
new()
{
Enabled = true,
RuleType = ERuleType.DNS,
OutboundTag = Global.DirectTag,
Domain = ["geosite:cn"],
},
}),
DomainStrategy = Global.AsIs,
DomainStrategy4Singbox = string.Empty,
},
};
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}");
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
await cfg.dns.servers.Where(s => s.tag.StartsWith(Global.SingboxDirectDNSTagPrefix)).Should().HaveCount(2);
await cfg.dns.servers.Where(s => s.tag.StartsWith(Global.SingboxRemoteDNSTagPrefix)).Should().HaveCount(2);
if (parallelQuery)
{
await cfg.dns.rules.Should().Contain(r => r.race == true);
}
}
[Test]
public async Task GenerateClientConfigContent_Hysteria2Realm_ShouldEmitHttpsServerUrl()
{

View File

@@ -97,11 +97,16 @@ public class Global
public const string StringFalse = "false";
public const int SqliteMaxBatchSize = 10000;
public const string SingboxDirectDNSTag = "direct_dns";
public const string SingboxRemoteDNSTag = "remote_dns";
public const string SingboxLocalDNSTag = "local_local";
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
public const string SingboxDirectDNSTagPrefix = "direct-dns-";
public const string SingboxRemoteDNSTagPrefix = "remote-dns-";
public const string SingboxDirectDNSTag = "direct-dns-1";
public const string SingboxRemoteDNSTag = "remote-dns-1";
public const string SingboxDirectDNSTagTemplate = "direct-dns-{0}";
public const string SingboxRemoteDNSTagTemplate = "remote-dns-{0}";
public const string SingboxLocalDNSTag = "local-local";
public const string SingboxHostsDNSTag = "hosts-dns";
public const string SingboxFakeDNSTag = "fake-dns";
public const string SingboxSrsDownloadHttpClientTag = "srs-download-http-client";
public const int Hysteria2DefaultHopInt = 30;
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";

View File

@@ -8,6 +8,7 @@ public class SingboxConfig
public List<Outbound4Sbox> outbounds { get; set; }
public List<Endpoints4Sbox>? endpoints { get; set; }
public Route4Sbox route { get; set; }
public List<HttpClient4Sbox>? http_clients { get; set; }
public Experimental4Sbox? experimental { get; set; }
}
@@ -24,6 +25,7 @@ public class Dns4Sbox
public List<Server4Sbox> servers { get; set; }
public List<Rule4Sbox> rules { get; set; }
public string? final { get; set; }
public bool? optimistic { get; set; }
public string? strategy { get; set; }
public bool? disable_cache { get; set; }
public bool? disable_expire { get; set; }
@@ -94,6 +96,11 @@ public class Rule4Sbox
public bool? rule_set_ip_cidr_match_source { get; set; }
public bool? rule_set_ip_cidr_accept_empty { get; set; }
public bool? tls_record_fragment { get; set; }
public string? preferred_by { get; set; }
public string? match_response { get; set; } // or bool
public string? tag { get; set; }
public bool? race { get; set; }
public bool? speculative { get; set; }
}
[Serializable]
@@ -108,7 +115,6 @@ public class Inbound4Sbox
public int? mtu { get; set; }
public bool? auto_route { get; set; }
public bool? strict_route { get; set; }
public bool? endpoint_independent_nat { get; set; }
public string? stack { get; set; }
public List<User4Sbox> users { get; set; }
public List<string>? route_exclude_address { get; set; }
@@ -318,10 +324,12 @@ public class Ruleset4Sbox
public string? format { get; set; }
public string? path { get; set; }
public string? url { get; set; }
public string? download_detour { get; set; }
public string? http_client { get; set; }
public string? update_interval { get; set; }
}
public class HttpClient4Sbox : BaseServer4Sbox;
public abstract class DialFields4Sbox
{
public string? detour { get; set; }

View File

@@ -17,17 +17,15 @@
"rule_set": [
"geosite-google"
],
"server": "remote",
"strategy": "prefer_ipv4"
"server": "remote"
},
{
"rule_set": [
"geosite-cn"
],
"server": "local",
"strategy": "prefer_ipv4"
"server": "local"
}
],
"final": "remote",
"strategy": "prefer_ipv4"
}
}

View File

@@ -17,15 +17,13 @@
"rule_set": [
"geosite-google"
],
"server": "remote",
"strategy": "prefer_ipv4"
"server": "remote"
},
{
"rule_set": [
"geosite-cn"
],
"server": "local",
"strategy": "prefer_ipv4"
"server": "local"
}
],
"final": "remote",

View File

@@ -17,36 +17,11 @@ public partial class CoreConfigSingboxService
GenDnsRules();
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.independent_cache = true;
_coreConfig.dns.optimistic = context.SimpleDnsItem.ServeStale == true ? true : null;
// final dns
var routing = context.RoutingItem;
var useDirectDns = false;
if (routing != null)
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
if (rules?.LastOrDefault() is { OutboundTag: Global.DirectTag } lastRule)
{
var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0");
var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535";
var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp";
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
}
}
var useDirectDns = UseDirectDns();
_coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
var simpleDnsItem = context.SimpleDnsItem;
if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxFakeDNSTag,
query_type = new List<int> { 1, 28 }, // A and AAAA
rewrite_ttl = 1,
});
}
}
catch (Exception ex)
{
@@ -59,14 +34,24 @@ public partial class CoreConfigSingboxService
var simpleDnsItem = context.SimpleDnsItem;
var finalDns = GenBootstrapDns();
var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First());
directDns.tag = Global.SingboxDirectDNSTag;
directDns.domain_resolver = Global.SingboxLocalDNSTag;
var directDnsList = ParseDnsAddresses(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First());
for (var i = 0; i < directDnsList.Count; i++)
{
var directDns = directDnsList[i];
var tag = string.Format(Global.SingboxDirectDNSTagTemplate, i + 1);
directDns.tag = tag;
directDns.domain_resolver = Global.SingboxLocalDNSTag;
}
var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
var remoteDnsList = ParseDnsAddresses(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
for (var i = 0; i < remoteDnsList.Count; i++)
{
var remoteDns = remoteDnsList[i];
var tag = string.Format(Global.SingboxRemoteDNSTagTemplate, i + 1);
remoteDns.tag = tag;
remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
}
var hostsDns = new Server4Sbox
{
@@ -118,26 +103,28 @@ public partial class CoreConfigSingboxService
{
finalDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (remoteDns.server == host.Key)
{
remoteDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (directDns.server == host.Key)
foreach (var directDns in directDnsList.Where(directDns => directDns.server == host.Key))
{
directDns.domain_resolver = Global.SingboxHostsDNSTag;
}
foreach (var remoteDns in remoteDnsList.Where(remoteDns => remoteDns.server == host.Key))
{
remoteDns.domain_resolver = Global.SingboxHostsDNSTag;
}
}
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.servers ??= [];
_coreConfig.dns.servers.Add(remoteDns);
_coreConfig.dns.servers.Add(directDns);
_coreConfig.dns.servers.AddRange(remoteDnsList);
_coreConfig.dns.servers.AddRange(directDnsList);
_coreConfig.dns.servers.Add(hostsDns);
// fake ip
if (simpleDnsItem.FakeIP == true)
{
var fakeipRange = simpleDnsItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : simpleDnsItem.FakeIPRange;
var fakeipRange = simpleDnsItem.FakeIPRange.IsNullOrEmpty()
? Global.FakeIPRanges.First()
: simpleDnsItem.FakeIPRange;
var fakeip = new Server4Sbox
{
tag = Global.SingboxFakeDNSTag,
@@ -164,33 +151,33 @@ public partial class CoreConfigSingboxService
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.rules ??= [];
_coreConfig.dns.rules.Add(new() { ip_accept_any = true, server = Global.SingboxHostsDNSTag });
_coreConfig.dns.rules.Add(new()
{
preferred_by = Global.SingboxHostsDNSTag,
server = Global.SingboxHostsDNSTag,
});
if (context.ProtectDomainList.Count > 0)
{
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial),
domain = context.ProtectDomainList.ToList(),
});
}
_coreConfig.dns.rules.AddRange(new[]
{
_coreConfig.dns.rules.AddRange([
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
clash_mode = nameof(ERuleMode.Global)
clash_mode = nameof(ERuleMode.Global),
},
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
clash_mode = nameof(ERuleMode.Direct)
}
});
clash_mode = nameof(ERuleMode.Direct),
},
]);
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{
@@ -199,7 +186,7 @@ public partial class CoreConfigSingboxService
{
continue;
}
var rule = new Rule4Sbox()
var rule = new Rule4Sbox
{
query_type = [1, 5, 28], // A, CNAME and AAAA
action = "predefined",
@@ -262,13 +249,14 @@ public partial class CoreConfigSingboxService
{
query_type = [64, 65],
action = "predefined",
rcode = "NOERROR"
rcode = "NOERROR",
});
}
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp != false)
{
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
var fakeipFilterRule =
JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
fakeipFilterRule.invert = true;
var rule4Fake = new Rule4Sbox
{
@@ -305,6 +293,11 @@ public partial class CoreConfigSingboxService
return;
}
var directDnsList = _coreConfig.dns.servers
.Where(s => s.tag?.StartsWith(Global.SingboxDirectDNSTagPrefix) == true).ToList();
var remoteDnsList = _coreConfig.dns.servers
.Where(s => s.tag?.StartsWith(Global.SingboxRemoteDNSTagPrefix) == true).ToList();
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new HashSet<string>();
var expectedIPsRegions = new HashSet<string>();
@@ -312,8 +305,7 @@ public partial class CoreConfigSingboxService
if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs))
{
var ipItems = simpleDnsItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
var ipItems = (Utils.String2List(simpleDnsItem.DirectExpectedIPs) ?? [])
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
.ToHashSet();
@@ -359,35 +351,60 @@ public partial class CoreConfigSingboxService
if (item.OutboundTag == Global.DirectTag)
{
rule.server = Global.SingboxDirectDNSTag;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
Rule4Sbox? rule4ExpectedIPs = null;
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0 && !regionName.IsNullOrEmpty())
{
var regionGeosite = rule.geosite.Where(g => g.EndsWith($"-{regionName}", StringComparison.OrdinalIgnoreCase)
|| g.EndsWith($"@{regionName}", StringComparison.OrdinalIgnoreCase)
|| g == regionName).ToList();
var regionGeosite = rule.geosite.Where(g =>
g.EndsWith($"-{regionName}", StringComparison.OrdinalIgnoreCase)
|| g.EndsWith($"@{regionName}", StringComparison.OrdinalIgnoreCase)
|| g == regionName).ToList();
if (regionGeosite.Count > 0)
{
rule.geosite.RemoveAll(regionGeosite.Contains);
var rule4ExpectedIPs = JsonUtils.DeepCopy(rule);
rule4ExpectedIPs = JsonUtils.DeepCopy(rule);
rule4ExpectedIPs.geosite = regionGeosite;
}
}
if (rule.geosite?.Count > 0 || rule.domain?.Count > 0)
{
AddRules(rule, item, directDnsList);
}
if (rule4ExpectedIPs is not null)
{
var expectedRuleList = BuildMultiRules(rule4ExpectedIPs, item, directDnsList);
foreach (var expectedRule in
expectedRuleList.Where(expectedRule => expectedRule.action == "respond"))
{
if (expectedIPsRegions.Count > 0)
{
rule4ExpectedIPs.geoip = expectedIPsRegions.ToList();
expectedRule.geoip = expectedIPsRegions.ToList();
}
if (expectedIPCidr.Count > 0)
{
rule4ExpectedIPs.ip_cidr = expectedIPCidr.ToList();
expectedRule.ip_cidr = expectedIPCidr.ToList();
}
_coreConfig.dns.rules.Add(rule4ExpectedIPs);
}
_coreConfig.dns.rules.AddRange(expectedRuleList);
var fallbackRemoteRuleList = BuildMultiRules(rule4ExpectedIPs, item, remoteDnsList);
if (expectedRuleList.LastOrDefault()?.race == true)
{
foreach (var fallbackRemoteRule in fallbackRemoteRuleList.Where(fallbackRemoteRule =>
fallbackRemoteRule.action == "evaluate"))
{
fallbackRemoteRule.speculative = true;
}
}
_coreConfig.dns.rules.AddRange(fallbackRemoteRuleList);
}
}
else if (item.OutboundTag == Global.BlockTag)
{
rule.action = "predefined";
rule.rcode = "NXDOMAIN";
_coreConfig.dns.rules.Add(rule);
}
else
{
@@ -395,15 +412,104 @@ public partial class CoreConfigSingboxService
{
var rule4Fake = JsonUtils.DeepCopy(rule);
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;
_coreConfig.dns.rules.Add(rule4Fake);
}
rule.server = Global.SingboxRemoteDNSTag;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy);
AddRules(rule, item, remoteDnsList);
}
}
var useDirectDns = UseDirectDns();
if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxFakeDNSTag,
query_type =
[
1,
28,
], // A and AAAA
rewrite_ttl = 1,
});
}
var tempRuleItem = new RulesItem
{
Id = $"final-{Utils.GetGuid(false)}",
Enabled = true,
};
AddRules(new(), tempRuleItem, useDirectDns ? directDnsList : remoteDnsList);
return;
_coreConfig.dns.rules.Add(rule);
static List<Rule4Sbox> BuildParallelRules(Rule4Sbox rule, RulesItem item, List<Server4Sbox> dnsList)
{
var evaluateRuleList = new List<Rule4Sbox>();
var racingMatchingRuleList = new List<Rule4Sbox>();
foreach (var dnsServer in dnsList)
{
var dnsServerTag = dnsServer.tag;
var evaluateRule = JsonUtils.DeepCopy(rule);
evaluateRule.action = "evaluate";
// Maybe rule index is better than id? Not sure, but id is unique, so use id for now.
var evaluateTag = $"{dnsServerTag}-{item.Id}";
evaluateRule.tag = evaluateTag;
evaluateRule.server = dnsServerTag;
evaluateRuleList.Add(evaluateRule);
var racingMatchingRule = new Rule4Sbox
{
match_response = evaluateTag,
race = true,
action = "respond",
};
racingMatchingRuleList.Add(racingMatchingRule);
}
return [.. evaluateRuleList, .. racingMatchingRuleList];
}
static List<Rule4Sbox> BuildSerialRules(Rule4Sbox rule, RulesItem item, List<Server4Sbox> dnsList)
{
var ruleList = new List<Rule4Sbox>();
foreach (var dnsServer in dnsList)
{
var dnsServerTag = dnsServer.tag;
var evaluateRule = JsonUtils.DeepCopy(rule);
evaluateRule.action = "evaluate";
// Maybe rule index is better than id? Not sure, but id is unique, so use id for now.
var evaluateTag = $"{dnsServerTag}-{item.Id}";
evaluateRule.tag = evaluateTag;
evaluateRule.server = dnsServerTag;
ruleList.Add(evaluateRule);
var racingMatchingRule = new Rule4Sbox
{
match_response = evaluateTag,
action = "respond",
};
ruleList.Add(racingMatchingRule);
}
return ruleList;
}
List<Rule4Sbox> BuildMultiRules(Rule4Sbox rule, RulesItem item, List<Server4Sbox> dnsList)
{
return simpleDnsItem.ParallelQuery == true ? BuildParallelRules(rule, item, dnsList) : BuildSerialRules(rule, item, dnsList);
}
void AddRules(Rule4Sbox rule, RulesItem item, List<Server4Sbox> dnsList)
{
if (dnsList.Count == 1)
{
rule.server = dnsList.First().tag;
_coreConfig.dns.rules.Add(rule);
}
else
{
var ruleList = BuildMultiRules(rule, item, dnsList);
_coreConfig.dns.rules.AddRange(ruleList);
}
}
}
@@ -432,11 +538,15 @@ public partial class CoreConfigSingboxService
var strDNS = string.Empty;
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;
}
else
{
strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS;
strDNS = string.IsNullOrEmpty(item?.NormalDNS)
? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName)
: item?.NormalDNS;
}
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
@@ -464,15 +574,18 @@ public partial class CoreConfigSingboxService
dns4Sbox.rules.Insert(0, new()
{
server = tag,
clash_mode = nameof(ERuleMode.Direct)
clash_mode = nameof(ERuleMode.Direct),
});
dns4Sbox.rules.Insert(0, new()
{
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote",
clash_mode = nameof(ERuleMode.Global)
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ??
"remote",
clash_mode = nameof(ERuleMode.Global),
});
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress)
? Global.DomainPureIPDNSAddress.FirstOrDefault()
: dnsItem?.DomainDNSAddress;
var localDnsServer = ParseDnsAddress(finalDnsAddress);
localDnsServer.tag = tag;
@@ -500,9 +613,21 @@ public partial class CoreConfigSingboxService
};
}
private static List<Server4Sbox> ParseDnsAddresses(string addresses)
{
var servers = new List<Server4Sbox>();
var addressList = Utils.String2List(addresses)?.ToList();
if (addressList is not { Count: > 0 })
{
return servers;
}
servers.AddRange(addressList.Select(ParseDnsAddress).Where(s => s != null));
return servers;
}
private static Server4Sbox? ParseDnsAddress(string address)
{
var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim();
var addressFirst = Utils.String2List(address)?.FirstOrDefault()?.Trim();
if (string.IsNullOrEmpty(addressFirst))
{
return null;
@@ -547,10 +672,33 @@ public partial class CoreConfigSingboxService
{
server.server_port = port;
}
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(path) && path != "/")
if (server.type is "https" or "h3" && !string.IsNullOrEmpty(path) && path != "/")
{
server.path = path;
}
return server;
}
private bool UseDirectDns()
{
var routing = context.RoutingItem;
var useDirectDns = false;
if (routing == null)
{
return useDirectDns;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
if (rules?.LastOrDefault() is not { OutboundTag: Global.DirectTag } lastRule)
{
return useDirectDns;
}
var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0");
var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535";
var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp";
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
return useDirectDns;
}
}

View File

@@ -244,7 +244,7 @@ public partial class CoreConfigSingboxService
var resolveRule = new Rule4Sbox
{
action = "resolve",
strategy = domainStrategy
strategy = domainStrategy,
};
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
{

View File

@@ -79,6 +79,7 @@ public partial class CoreConfigSingboxService
//Add ruleset srs
_coreConfig.route.rule_set = [];
var containRemoteRuleset = false;
foreach (var item in new HashSet<string>(ruleSets))
{
if (item.IsNullOrEmpty())
@@ -94,11 +95,13 @@ public partial class CoreConfigSingboxService
type = "local",
format = "binary",
tag = item,
path = pathSrs
path = pathSrs,
};
}
else
{
containRemoteRuleset = true;
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
? Global.SingboxRulesetUrl
: _config.ConstItem.SrsSourceUrl;
@@ -109,11 +112,21 @@ public partial class CoreConfigSingboxService
format = "binary",
tag = item,
url = string.Format(srsUrl, item.StartsWith(geosite) ? geosite : geoip, item),
download_detour = Global.ProxyTag
http_client = Global.SingboxSrsDownloadHttpClientTag,
};
}
}
_coreConfig.route.rule_set.Add(customRuleset);
}
if (containRemoteRuleset)
{
_coreConfig.http_clients ??= [];
_coreConfig.http_clients.Add(new()
{
tag = Global.SingboxSrsDownloadHttpClientTag,
detour = Global.ProxyTag,
});
}
}
}

View File

@@ -197,8 +197,7 @@ public partial class CoreConfigV2rayService
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
{
expectedIPs = simpleDNSItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
expectedIPs = (Utils.String2List(simpleDNSItem.DirectExpectedIPs) ?? [])
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
.ToHashSet();
@@ -335,7 +334,7 @@ public partial class CoreConfigV2rayService
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
var addresses = (Utils.String2List(dnsInput) ?? [])
.Select(addr => addr.Trim())
.Where(addr => !string.IsNullOrEmpty(addr))
.Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr)