v2rayN/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs

510 lines
16 KiB
C#
Raw Normal View History

namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
2026-02-06 04:55:55 +00:00
private void GenDns()
{
try
{
2026-02-06 04:55:55 +00:00
var item = context.RawDnsItem;
if (item is { Enabled: true })
{
2026-02-06 04:55:55 +00:00
GenDnsCustom();
return;
}
2026-02-06 04:55:55 +00:00
GenDnsServers();
GenDnsRules();
2026-02-06 04:55:55 +00:00
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.independent_cache = true;
2025-08-19 01:10:54 +00:00
// final dns
2026-02-06 04:55:55 +00:00
var routing = context.RoutingItem;
var useDirectDns = false;
if (routing != null)
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
useDirectDns = rules?.LastOrDefault() is { } lastRule &&
lastRule.OutboundTag == Global.DirectTag &&
(lastRule.Port == "0-65535" ||
lastRule.Network == "tcp,udp" ||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
}
2026-02-06 04:55:55 +00:00
_coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
var simpleDnsItem = context.SimpleDnsItem;
if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxFakeDNSTag,
query_type = new List<int> { 1, 28 }, // A and AAAA
rewrite_ttl = 1,
});
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
2026-02-06 04:55:55 +00:00
private void GenDnsServers()
{
2026-02-06 04:55:55 +00:00
var simpleDnsItem = context.SimpleDnsItem;
var finalDns = GenBootstrapDns();
2026-02-06 04:55:55 +00:00
var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First());
directDns.tag = Global.SingboxDirectDNSTag;
2025-10-11 11:22:26 +00:00
directDns.domain_resolver = Global.SingboxLocalDNSTag;
2026-02-06 04:55:55 +00:00
var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag;
2025-10-11 11:22:26 +00:00
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
var hostsDns = new Server4Sbox
{
tag = Global.SingboxHostsDNSTag,
type = "hosts",
predefined = new(),
};
2026-02-06 04:55:55 +00:00
if (simpleDnsItem.AddCommonHosts == true)
{
hostsDns.predefined = Global.PredefinedHosts;
}
2026-02-06 04:55:55 +00:00
if (simpleDnsItem.UseSystemHosts == true)
2025-08-26 09:34:12 +00:00
{
var systemHosts = Utils.GetSystemHosts();
if (systemHosts != null && systemHosts.Count > 0)
{
foreach (var host in systemHosts)
{
hostsDns.predefined.TryAdd(host.Key, new List<string> { host.Value });
}
}
}
2026-02-06 04:55:55 +00:00
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{
2026-02-06 04:55:55 +00:00
hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList();
}
foreach (var host in hostsDns.predefined)
{
if (finalDns.server == host.Key)
{
finalDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (remoteDns.server == host.Key)
{
remoteDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (directDns.server == host.Key)
{
directDns.domain_resolver = Global.SingboxHostsDNSTag;
}
}
2026-02-06 04:55:55 +00:00
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.servers ??= [];
_coreConfig.dns.servers.Add(remoteDns);
_coreConfig.dns.servers.Add(directDns);
_coreConfig.dns.servers.Add(hostsDns);
// fake ip
2026-02-06 04:55:55 +00:00
if (simpleDnsItem.FakeIP == true)
{
var fakeip = new Server4Sbox
{
tag = Global.SingboxFakeDNSTag,
type = "fakeip",
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
};
2026-02-06 04:55:55 +00:00
_coreConfig.dns.servers.Add(fakeip);
}
}
2026-02-06 04:55:55 +00:00
private Server4Sbox GenBootstrapDns()
{
2026-02-06 04:55:55 +00:00
var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First());
2025-10-11 11:22:26 +00:00
finalDns.tag = Global.SingboxLocalDNSTag;
2026-02-06 04:55:55 +00:00
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.servers ??= [];
_coreConfig.dns.servers.Add(finalDns);
return finalDns;
}
2026-02-06 04:55:55 +00:00
private void GenDnsRules()
{
2026-02-06 04:55:55 +00:00
var simpleDnsItem = context.SimpleDnsItem;
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.rules ??= [];
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.AddRange(new[]
{
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
new Rule4Sbox
2026-02-06 04:55:55 +00:00
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
domain = context.ProtectDomainList.ToList(),
},
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
2026-02-06 04:55:55 +00:00
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
clash_mode = ERuleMode.Global.ToString()
},
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
2026-02-06 04:55:55 +00:00
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
clash_mode = ERuleMode.Direct.ToString()
}
});
2026-02-06 04:55:55 +00:00
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
2026-02-05 09:05:27 +00:00
{
var predefined = kvp.Value.First();
if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined))
{
continue;
}
if (predefined.StartsWith('#') && int.TryParse(predefined.AsSpan(1), out var rcode))
{
// xray syntactic sugar for predefined
// etc. #0 -> NOERROR
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Add(new()
2026-02-05 09:05:27 +00:00
{
query_type = [1, 28],
domain = [kvp.Key],
action = "predefined",
rcode = rcode switch
{
0 => "NOERROR",
1 => "FORMERR",
2 => "SERVFAIL",
3 => "NXDOMAIN",
4 => "NOTIMP",
5 => "REFUSED",
_ => "NOERROR",
},
});
continue;
}
// CNAME record
Rule4Sbox rule = new()
{
query_type = [1, 28],
action = "predefined",
rcode = "NOERROR",
answer = [$"*. IN CNAME {predefined}."],
};
if (ParseV2Domain(kvp.Key, rule))
{
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Add(rule);
2026-02-05 09:05:27 +00:00
}
}
2026-02-06 04:55:55 +00:00
if (simpleDnsItem.BlockBindingQuery == true)
{
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Add(new()
{
2026-02-05 09:05:27 +00:00
query_type = [64, 65],
action = "predefined",
2026-02-05 09:05:27 +00:00
rcode = "NOERROR"
});
}
2026-02-06 04:55:55 +00:00
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
{
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
fakeipFilterRule.invert = true;
var rule4Fake = new Rule4Sbox
{
server = Global.SingboxFakeDNSTag,
type = "logical",
mode = "and",
rewrite_ttl = 1,
2026-02-05 09:05:27 +00:00
rules =
[
new()
{
query_type = [1, 28], // A and AAAA
},
2026-02-05 09:05:27 +00:00
fakeipFilterRule
]
};
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Add(rule4Fake);
}
2026-02-06 04:55:55 +00:00
var routing = context.RoutingItem;
if (routing == null)
2025-11-02 07:25:41 +00:00
{
2026-02-06 04:55:55 +00:00
return;
2025-11-02 07:25:41 +00:00
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new List<string>();
var expectedIPsRegions = new List<string>();
var regionNames = new HashSet<string>();
2026-02-06 04:55:55 +00:00
if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs))
{
2026-02-06 04:55:55 +00:00
var ipItems = simpleDnsItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
.ToList();
foreach (var ip in ipItems)
{
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
{
var region = ip["geoip:".Length..];
if (!string.IsNullOrEmpty(region))
{
expectedIPsRegions.Add(region);
regionNames.Add(region);
regionNames.Add($"geolocation-{region}");
regionNames.Add($"tld-{region}");
}
}
else
{
expectedIPCidr.Add(ip);
}
}
}
foreach (var item in rules)
{
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
{
continue;
}
if (item.RuleType == ERuleType.Routing)
{
continue;
}
var rule = new Rule4Sbox();
var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule));
if (validDomains <= 0)
{
continue;
}
if (item.OutboundTag == Global.DirectTag)
{
rule.server = Global.SingboxDirectDNSTag;
2026-02-06 04:55:55 +00:00
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
{
var geositeSet = new HashSet<string>(rule.geosite);
if (regionNames.Intersect(geositeSet).Any())
{
if (expectedIPsRegions.Count > 0)
{
rule.geoip = expectedIPsRegions;
}
if (expectedIPCidr.Count > 0)
{
rule.ip_cidr = expectedIPCidr;
}
}
}
}
else if (item.OutboundTag == Global.BlockTag)
{
rule.action = "predefined";
rule.rcode = "NXDOMAIN";
}
else
{
2026-02-06 04:55:55 +00:00
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
var rule4Fake = JsonUtils.DeepCopy(rule);
rule4Fake.server = Global.SingboxFakeDNSTag;
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
rule4Fake.rewrite_ttl = 1;
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Add(rule4Fake);
}
rule.server = Global.SingboxRemoteDNSTag;
2026-02-06 04:55:55 +00:00
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy);
}
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Add(rule);
}
2026-02-06 04:55:55 +00:00
}
2026-02-06 04:55:55 +00:00
private void GenMinimizedDns()
{
GenDnsServers();
2026-02-08 06:26:39 +00:00
foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList())
{
_coreConfig.dns.servers.Remove(server);
}
2026-02-06 04:55:55 +00:00
_coreConfig.dns ??= new();
2026-02-06 07:24:14 +00:00
_coreConfig.dns.rules ??= [];
2026-02-06 04:55:55 +00:00
_coreConfig.dns.rules.Clear();
_coreConfig.dns.final = Global.SingboxDirectDNSTag;
_coreConfig.route.default_domain_resolver = new()
{
server = Global.SingboxDirectDNSTag,
};
}
2026-02-06 04:55:55 +00:00
private void GenDnsCustom()
{
try
{
2026-02-06 04:55:55 +00:00
var item = context.RawDnsItem;
var strDNS = string.Empty;
2026-02-06 04:55:55 +00:00
if (context.IsTunEnabled)
{
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
}
else
{
strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS;
}
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
if (dns4Sbox is null)
{
2026-02-06 04:55:55 +00:00
return;
}
2026-02-06 04:55:55 +00:00
_coreConfig.dns = dns4Sbox;
2026-02-06 07:20:18 +00:00
if (dns4Sbox.servers?.Count > 0 &&
dns4Sbox.servers.First().address.IsNullOrEmpty())
{
GenDnsProtectCustom();
}
else
{
GenDnsProtectCustomLegacy();
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
2026-02-06 04:55:55 +00:00
private void GenDnsProtectCustom()
{
2026-02-06 04:55:55 +00:00
var dnsItem = context.RawDnsItem;
var dns4Sbox = _coreConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
2025-10-11 11:22:26 +00:00
var tag = Global.SingboxLocalDNSTag;
2026-02-06 07:20:18 +00:00
dns4Sbox.rules.Insert(0, new()
{
server = tag,
clash_mode = ERuleMode.Direct.ToString()
});
dns4Sbox.rules.Insert(0, new()
{
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote",
clash_mode = ERuleMode.Global.ToString()
});
2025-10-11 11:22:26 +00:00
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
2025-10-11 11:22:26 +00:00
var localDnsServer = ParseDnsAddress(finalDnsAddress);
localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer);
2026-02-06 04:55:55 +00:00
dns4Sbox.rules.Insert(0, BuildProtectDomainRule());
2026-02-06 04:55:55 +00:00
_coreConfig.dns = dns4Sbox;
}
2026-02-06 07:20:18 +00:00
private void GenDnsProtectCustomLegacy()
{
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;
}
2026-02-06 04:55:55 +00:00
private Rule4Sbox BuildProtectDomainRule()
{
2026-02-06 04:55:55 +00:00
return new()
{
2025-10-11 11:22:26 +00:00
server = Global.SingboxLocalDNSTag,
2026-02-06 04:55:55 +00:00
domain = context.ProtectDomainList.ToList(),
};
}
private static Server4Sbox? ParseDnsAddress(string address)
{
var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim();
if (string.IsNullOrEmpty(addressFirst))
{
return null;
}
var server = new Server4Sbox();
if (addressFirst is "local" or "localhost")
{
server.type = "local";
return server;
}
var (domain, scheme, port, path) = Utils.ParseUrl(addressFirst);
if (scheme.Equals("dhcp", StringComparison.OrdinalIgnoreCase))
{
server.type = "dhcp";
if ((!domain.IsNullOrEmpty()) && domain != "auto")
{
server.server = domain;
}
return server;
}
if (scheme.IsNullOrEmpty())
{
// udp dns
server.type = "udp";
2025-10-22 01:07:53 +00:00
}
else
{
// server.type = scheme.ToLower();
// remove "+local" suffix
// TODO: "+local" suffix decide server.detour = "direct" ?
server.type = scheme.Replace("+local", "", StringComparison.OrdinalIgnoreCase).ToLower();
}
server.server = domain;
if (port != 0)
{
server.server_port = port;
}
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(path) && path != "/")
{
server.path = path;
}
return server;
}
}