mirror of
https://github.com/2dust/v2rayN.git
synced 2026-09-17 11:52:06 +03:00
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:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ public partial class CoreConfigSingboxService
|
||||
var resolveRule = new Rule4Sbox
|
||||
{
|
||||
action = "resolve",
|
||||
strategy = domainStrategy
|
||||
strategy = domainStrategy,
|
||||
};
|
||||
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user