using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private async Task GenDns(ProfileItem? node, V2rayConfig v2rayConfig) { try { var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); if (item != null && item.Enabled == true) { var result = await GenDnsCompatible(node, v2rayConfig); if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) { // DNS routing v2rayConfig.dns.tag = Global.DnsTag; v2rayConfig.routing.rules.Add(new RulesItem4Ray { type = "field", inboundTag = new List { Global.DnsTag }, outboundTag = Global.ProxyTag, }); } return result; } var simpleDNSItem = _config.SimpleDNSItem; var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom; //Outbound Freedom domainStrategy if (domainStrategy4Freedom.IsNotEmpty()) { var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new() { domainStrategy = domainStrategy4Freedom, userLevel = 0 }; } } await GenDnsServers(node, v2rayConfig, simpleDNSItem); await GenDnsHosts(v2rayConfig, simpleDNSItem); if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) { // DNS routing v2rayConfig.dns.tag = Global.DnsTag; v2rayConfig.routing.rules.Add(new RulesItem4Ray { type = "field", inboundTag = new List { Global.DnsTag }, outboundTag = Global.ProxyTag, }); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return 0; } private async Task GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem) { static List ParseDnsAddresses(string? dnsInput, string defaultAddress) { var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';') .Select(addr => addr.Trim()) .Where(addr => !string.IsNullOrEmpty(addr)) .Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr) .Distinct() .ToList() ?? new List { defaultAddress }; return addresses.Count > 0 ? addresses : new List { defaultAddress }; } static object CreateDnsServer(string dnsAddress, List domains, List? expectedIPs = null) { var dnsServer = new DnsServer4Ray { address = dnsAddress, skipFallback = true, domains = domains.Count > 0 ? domains : null, expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null }; return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); } var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault()); var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault()); var directDomainList = new List(); var directGeositeList = new List(); var proxyDomainList = new List(); var proxyGeositeList = new List(); var expectedDomainList = new List(); var expectedIPs = new List(); var regionNames = new HashSet(); if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) { expectedIPs = simpleDNSItem.DirectExpectedIPs .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) .ToList(); foreach (var ip in expectedIPs) { if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase)) { var region = ip["geoip:".Length..]; if (!string.IsNullOrEmpty(region)) { regionNames.Add($"geosite:{region}"); regionNames.Add($"geosite:geolocation-{region}"); regionNames.Add($"geosite:tld-{region}"); } } } } var routing = await ConfigHandler.GetDefaultRouting(_config); List? rules = null; if (routing != null) { rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; foreach (var item in rules) { if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) { continue; } if (item.RuleType == ERuleType.Routing) { continue; } foreach (var domain in item.Domain) { if (domain.StartsWith('#')) continue; var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ","); if (item.OutboundTag == Global.DirectTag) { if (normalizedDomain.StartsWith("geosite:")) { (regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain); } else { directDomainList.Add(normalizedDomain); } } else if (item.OutboundTag != Global.BlockTag) { if (normalizedDomain.StartsWith("geosite:")) { proxyGeositeList.Add(normalizedDomain); } else { proxyDomainList.Add(normalizedDomain); } } } } } if (Utils.IsDomain(node?.Address)) { directDomainList.Add(node.Address); } 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); } } } } v2rayConfig.dns ??= new Dns4Ray(); v2rayConfig.dns.servers ??= new List(); void AddDnsServers(List dnsAddresses, List domains, List? expectedIPs = null) { if (domains.Count > 0) { foreach (var dnsAddress in dnsAddresses) { v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs)); } } } AddDnsServers(remoteDNSAddress, proxyDomainList); AddDnsServers(directDNSAddress, directDomainList); AddDnsServers(remoteDNSAddress, proxyGeositeList); AddDnsServers(directDNSAddress, directGeositeList); AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs); var 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); var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; v2rayConfig.dns.servers.AddRange(defaultDnsServers); return 0; } private async Task GenDnsHosts(V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem) { if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty()) { return await Task.FromResult(0); } v2rayConfig.dns ??= new Dns4Ray(); v2rayConfig.dns.hosts ??= new Dictionary(); if (simpleDNSItem.AddCommonHosts == true) { v2rayConfig.dns.hosts = Global.PredefinedHosts.ToDictionary( kvp => kvp.Key, kvp => (object)kvp.Value ); } if (simpleDNSItem.UseSystemHosts == true) { var systemHosts = Utils.GetSystemHosts(); var normalHost = v2rayConfig?.dns?.hosts; if (normalHost != null && systemHosts?.Count > 0) { foreach (var host in systemHosts) { normalHost.TryAdd(host.Key, new List { host.Value }); } } } if (!simpleDNSItem.Hosts.IsNullOrEmpty()) { var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts); foreach (var kvp in userHostsMap) { v2rayConfig.dns.hosts[kvp.Key] = kvp.Value; } } return await Task.FromResult(0); } private async Task GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig) { try { var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); var normalDNS = item?.NormalDNS; var domainStrategy4Freedom = item?.DomainStrategy4Freedom; if (normalDNS.IsNullOrEmpty()) { normalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); } //Outbound Freedom domainStrategy if (domainStrategy4Freedom.IsNotEmpty()) { var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new(); outbound.settings.domainStrategy = domainStrategy4Freedom; outbound.settings.userLevel = 0; } } var obj = JsonUtils.ParseJson(normalDNS); if (obj is null) { List servers = []; string[] arrDNS = normalDNS.Split(','); foreach (string str in arrDNS) { servers.Add(str); } obj = JsonUtils.ParseJson("{}"); obj["servers"] = JsonUtils.SerializeToNode(servers); } // Append to dns settings if (item.UseSystemHosts) { var systemHosts = Utils.GetSystemHosts(); if (systemHosts.Count > 0) { var normalHost1 = obj["hosts"]; if (normalHost1 != null) { foreach (var host in systemHosts) { if (normalHost1[host.Key] != null) continue; normalHost1[host.Key] = host.Value; } } } } var normalHost = obj["hosts"]; if (normalHost != null) { foreach (var hostProp in normalHost.AsObject().ToList()) { if (hostProp.Value is JsonValue value && value.TryGetValue(out var ip)) { normalHost[hostProp.Key] = new JsonArray(ip); } } } await GenDnsDomainsCompatible(node, obj, item); v2rayConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return 0; } private async Task GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dnsItem) { if (node == null) { return 0; } var servers = dns["servers"]; if (servers != null) { var domainList = new List(); 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 nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType) && Utils.IsDomain(nextNode.Address)) { domainList.Add(nextNode.Address); } } if (domainList.Count > 0) { var dnsServer = new DnsServer4Ray() { address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, skipFallback = true, domains = domainList }; servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); } } return await Task.FromResult(0); } }