diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index 43302636..ad9a4029 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -63,6 +63,7 @@ public sealed class AppHandler SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); return true; } @@ -192,6 +193,16 @@ public sealed class AppHandler return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Id == id); } + public async Task?> DNSItems() + { + return await SQLiteHelper.Instance.TableAsync().ToListAsync(); + } + + public async Task GetDNSItem(ECoreType eCoreType) + { + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); + } + #endregion SqliteHelper #region Core Type diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 78d8aa81..fd80160b 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -2100,6 +2100,101 @@ public class ConfigHandler #endregion Routing + #region DNS + + /// + /// Initialize built-in DNS configurations + /// Creates default DNS items for V2Ray and sing-box + /// + /// Current configuration + /// 0 if successful + public static async Task InitBuiltinDNS(Config config) + { + var items = await AppHandler.Instance.DNSItems(); + if (items.Count <= 0) + { + var item = new DNSItem() + { + Remarks = "V2ray", + CoreType = ECoreType.Xray, + }; + await SaveDNSItems(config, item); + + var item2 = new DNSItem() + { + Remarks = "sing-box", + CoreType = ECoreType.sing_box, + }; + await SaveDNSItems(config, item2); + } + + return 0; + } + + /// + /// Save a DNS item to the database + /// + /// Current configuration + /// DNS item to save + /// 0 if successful, -1 if failed + public static async Task SaveDNSItems(Config config, DNSItem item) + { + if (item == null) + { + return -1; + } + + if (item.Id.IsNullOrEmpty()) + { + item.Id = Utils.GetGuid(false); + } + + if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) + { + return 0; + } + else + { + return -1; + } + } + + /// + /// Get an external DNS configuration from URL + /// Downloads and processes DNS templates + /// + /// Core type (Xray or sing-box) + /// URL of the DNS template + /// DNS item with configuration from the URL + public static async Task GetExternalDNSItem(ECoreType type, string url) + { + var currentItem = await AppHandler.Instance.GetDNSItem(type); + + var downloadHandle = new DownloadService(); + var templateContent = await downloadHandle.TryDownloadString(url, true, ""); + if (templateContent.IsNullOrEmpty()) + return currentItem; + + var template = JsonUtils.Deserialize(templateContent); + if (template == null) + return currentItem; + + if (!template.NormalDNS.IsNullOrEmpty()) + template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, ""); + + if (!template.TunDNS.IsNullOrEmpty()) + template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, ""); + + template.Id = currentItem.Id; + template.Enabled = currentItem.Enabled; + template.Remarks = currentItem.Remarks; + template.CoreType = type; + + return template; + } + + #endregion DNS + #region Regional Presets /// @@ -2118,7 +2213,8 @@ public class ConfigHandler config.ConstItem.SrsSourceUrl = ""; config.ConstItem.RouteRulesTemplateSourceUrl = ""; - //await SQLiteHelper.Instance.DeleteAllAsync(); + await SQLiteHelper.Instance.DeleteAllAsync(); + await InitBuiltinDNS(config); return true; @@ -2127,8 +2223,8 @@ public class ConfigHandler config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1]; config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1]; - //await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json")); - //await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json")); + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json")); + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json")); return true; @@ -2137,8 +2233,8 @@ public class ConfigHandler config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2]; config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2]; - //await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json")); - //await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json")); + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json")); + await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json")); return true; } diff --git a/v2rayN/ServiceLib/Models/DNSItem.cs b/v2rayN/ServiceLib/Models/DNSItem.cs new file mode 100644 index 00000000..9474d906 --- /dev/null +++ b/v2rayN/ServiceLib/Models/DNSItem.cs @@ -0,0 +1,19 @@ +using SQLite; + +namespace ServiceLib.Models; + +[Serializable] +public class DNSItem +{ + [PrimaryKey] + public string Id { get; set; } + + public string Remarks { get; set; } + public bool Enabled { get; set; } = false; + public ECoreType CoreType { get; set; } + public bool UseSystemHosts { get; set; } + public string? NormalDNS { get; set; } + public string? TunDNS { get; set; } + public string? DomainStrategy4Freedom { get; set; } + public string? DomainDNSAddress { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index 5fd91408..cbaf77e6 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -239,6 +239,12 @@ public class Server4Sbox : BaseServer4Sbox public Headers4Sbox? headers { get; set; } // public List? path { get; set; } // hosts public Dictionary>? predefined { get; set; } + // Deprecated + public string? address { get; set; } + public string? address_resolver { get; set; } + public string? address_strategy { get; set; } + public string? strategy { get; set; } + // Deprecated End } public class Experimental4Sbox diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index b3a9ce07..be6fea63 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -2355,6 +2355,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Enable Custom DNS 的本地化字符串。 + /// + public static string TbCustomDNSEnable { + get { + return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture); + } + } + + /// + /// 查找类似 Custom DNS Enabled, This Page's Settings Invalid 的本地化字符串。 + /// + public static string TbCustomDNSEnabledPageInvalid { + get { + return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture); + } + } + /// /// 查找类似 Display GUI 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 075f6585..e772a8e6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1458,4 +1458,10 @@ When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index f861b85e..f161adcc 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1458,4 +1458,10 @@ When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index c9ecc6f7..ffb5b1cd 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1458,4 +1458,10 @@ When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 0358cf03..63e31f51 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1458,4 +1458,10 @@ When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index e752340a..a99f4680 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1455,4 +1455,10 @@ 配置后,会对相应地区域名(如 geosite:cn)的返回 IP 进行校验,仅返回期望 IP + + 启用自定义 DNS + + + 自定义 DNS 已启用,此页面配置将无效 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 92cecb9e..4b7a44a5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1455,4 +1455,10 @@ When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs + + Enable Custom DNS + + + Custom DNS Enabled, This Page's Settings Invalid + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 576bdd3d..90df8df6 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -245,7 +245,15 @@ public class CoreConfigSingboxService singboxConfig.route.rules.Add(rule); } - await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); + var rawDNSItem = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + if (rawDNSItem != null && rawDNSItem.Enabled == true) + { + await GenDnsDomainsCompatible(singboxConfig, rawDNSItem); + } + else + { + await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); + } //var dnsServer = singboxConfig.dns?.servers.FirstOrDefault(); //if (dnsServer != null) //{ @@ -317,7 +325,15 @@ public class CoreConfigSingboxService await GenOutbound(node, singboxConfig.outbounds.First()); } await GenMoreOutbounds(node, singboxConfig); - await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + if (item != null && item.Enabled == true) + { + await GenDnsDomainsCompatible(singboxConfig, item); + } + else + { + await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); + } singboxConfig.route.rules.Clear(); singboxConfig.inbounds.Clear(); @@ -1575,6 +1591,12 @@ public class CoreConfigSingboxService { try { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + if (item != null && item.Enabled == true) + { + return await GenDnsCompatible(singboxConfig); + } + var simpleDNSItem = _config.SimpleDNSItem; await GenDnsServers(singboxConfig, simpleDNSItem); await GenDnsRules(singboxConfig, simpleDNSItem); @@ -1819,6 +1841,138 @@ public class CoreConfigSingboxService return 0; } + private async Task GenDnsCompatible(SingboxConfig singboxConfig) + { + try + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + var strDNS = string.Empty; + if (_config.TunModeItem.EnableTun) + { + 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(strDNS); + if (dns4Sbox is null) + { + return 0; + } + singboxConfig.dns = dns4Sbox; + + if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) + { + await GenDnsDomainsCompatible(singboxConfig, item); + } + else + { + await GenDnsDomainsLegacyCompatible(singboxConfig, item); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dNSItem) + { + var dns4Sbox = singboxConfig.dns ?? new(); + dns4Sbox.servers ??= []; + dns4Sbox.rules ??= []; + + var tag = "outbound_resolver"; + var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress; + + if (localDnsAddress.StartsWith("tag://")) + { + tag = localDnsAddress.Substring(6); + + var localDnsTag = "local_local"; + + dns4Sbox.servers.Add(new() + { + tag = localDnsTag, + type = "local" + }); + + dns4Sbox.rules.Insert(0, new() + { + server = localDnsTag, + clash_mode = ERuleMode.Direct.ToString() + }); + } + else + { + var localDnsServer = ParseDnsAddress(localDnsAddress); + localDnsServer.tag = tag; + + dns4Sbox.servers.Add(localDnsServer); + + 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() + }); + + singboxConfig.dns = dns4Sbox; + return await Task.FromResult(0); + } + + private async Task GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dNSItem) + { + var dns4Sbox = singboxConfig.dns ?? new(); + dns4Sbox.servers ??= []; + dns4Sbox.rules ??= []; + + var tag = "local_local"; + 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() + { + 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() + }); + + var lstDomain = singboxConfig.outbounds + .Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server)) + .Select(t => t.server) + .Distinct() + .ToList(); + if (lstDomain != null && lstDomain.Count > 0) + { + dns4Sbox.rules.Insert(0, new() + { + server = tag, + domain = lstDomain + }); + } + + singboxConfig.dns = dns4Sbox; + return await Task.FromResult(0); + } + private static Server4Sbox? ParseDnsAddress(string address) { var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim(); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index cdc90e4b..6cb570b0 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -1137,8 +1137,13 @@ public class CoreConfigV2rayService { try { - var dNSItem = _config.SimpleDNSItem; - var domainStrategy4Freedom = dNSItem?.RayStrategy4Freedom; + var item = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + if (item != null && item.Enabled == true) + { + return await GenDnsCompatible(node, v2rayConfig); + } + var simpleDNSItem = _config.SimpleDNSItem; + var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom; //Outbound Freedom domainStrategy if (domainStrategy4Freedom.IsNotEmpty()) @@ -1154,8 +1159,8 @@ public class CoreConfigV2rayService } } - await GenDnsServers(node, v2rayConfig, dNSItem); - await GenDnsHosts(v2rayConfig, dNSItem); + await GenDnsServers(node, v2rayConfig, simpleDNSItem); + await GenDnsHosts(v2rayConfig, simpleDNSItem); } catch (Exception ex) { @@ -1378,6 +1383,137 @@ public class CoreConfigV2rayService return await Task.FromResult(0); } + private async Task GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig) + { + try + { + var item = await AppHandler.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(obj.ToJsonString()); + } + 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 AppHandler.Instance.GetSubItem(node.Subid); + if (subItem is not null) + { + // Previous proxy + var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode is not null + && prevNode.ConfigType != EConfigType.Custom + && prevNode.ConfigType != EConfigType.Hysteria2 + && prevNode.ConfigType != EConfigType.TUIC + && Utils.IsDomain(prevNode.Address)) + { + domainList.Add(prevNode.Address); + } + + // Next proxy + var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + if (nextNode is not null + && nextNode.ConfigType != EConfigType.Custom + && nextNode.ConfigType != EConfigType.Hysteria2 + && nextNode.ConfigType != EConfigType.TUIC + && 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); + } + private async Task GenStatistic(V2rayConfig v2rayConfig) { if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) diff --git a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs index b2cea774..e6e985ca 100644 --- a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs @@ -20,9 +20,21 @@ public class DNSSettingViewModel : MyReactiveObject [Reactive] public string? Hosts { get; set; } [Reactive] public string? DirectExpectedIPs { get; set; } + [Reactive] public bool UseSystemHostsCompatible { get; set; } + [Reactive] public string DomainStrategy4FreedomCompatible { get; set; } + [Reactive] public string DomainDNSAddressCompatible { get; set; } + [Reactive] public string NormalDNSCompatible { get; set; } + + [Reactive] public string DomainStrategy4Freedom2Compatible { get; set; } + [Reactive] public string DomainDNSAddress2Compatible { get; set; } + [Reactive] public string NormalDNS2Compatible { get; set; } + [Reactive] public string TunDNS2Compatible { get; set; } + [Reactive] public bool RayCustomDNSEnableCompatible { get; set; } + [Reactive] public bool SBCustomDNSEnableCompatible { get; set; } + public ReactiveCommand SaveCmd { get; } - //public ReactiveCommand ImportDefConfig4V2rayCmd { get; } - //public ReactiveCommand ImportDefConfig4SingboxCmd { get; } + public ReactiveCommand ImportDefConfig4V2rayCompatibleCmd { get; } + public ReactiveCommand ImportDefConfig4SingboxCompatibleCmd { get; } public DNSSettingViewModel(Func>? updateView) { @@ -30,18 +42,18 @@ public class DNSSettingViewModel : MyReactiveObject _updateView = updateView; SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync); - //ImportDefConfig4V2rayCmd = ReactiveCommand.CreateFromTask(async () => - //{ - // NormalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); - // await Task.CompletedTask; - //}); + ImportDefConfig4V2rayCompatibleCmd = ReactiveCommand.CreateFromTask(async () => + { + NormalDNSCompatible = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); + await Task.CompletedTask; + }); - //ImportDefConfig4SingboxCmd = ReactiveCommand.CreateFromTask(async () => - //{ - // NormalDNS2 = EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName); - // TunDNS2 = EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName); - // await Task.CompletedTask; - //}); + ImportDefConfig4SingboxCompatibleCmd = ReactiveCommand.CreateFromTask(async () => + { + NormalDNS2Compatible = EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName); + TunDNS2Compatible = EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName); + await Task.CompletedTask; + }); _ = Init(); } @@ -63,6 +75,20 @@ public class DNSSettingViewModel : MyReactiveObject SingboxStrategy4Proxy = item.SingboxStrategy4Proxy; Hosts = item.Hosts; DirectExpectedIPs = item.DirectExpectedIPs; + + var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + RayCustomDNSEnableCompatible = item1.Enabled; + UseSystemHostsCompatible = item1.UseSystemHosts; + DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty; + DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty; + NormalDNSCompatible = item1?.NormalDNS ?? string.Empty; + + var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + SBCustomDNSEnableCompatible = item2.Enabled; + DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty; + DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty; + NormalDNS2Compatible = item2?.NormalDNS ?? string.Empty; + TunDNS2Compatible = item2?.TunDNS ?? string.Empty; } private async Task SaveSettingAsync() @@ -80,6 +106,57 @@ public class DNSSettingViewModel : MyReactiveObject _config.SimpleDNSItem.SingboxStrategy4Proxy = SingboxStrategy4Proxy; _config.SimpleDNSItem.Hosts = Hosts; _config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs; + + if (NormalDNSCompatible.IsNotEmpty()) + { + var obj = JsonUtils.ParseJson(NormalDNSCompatible); + if (obj != null && obj["servers"] != null) + { + } + else + { + if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}')) + { + NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); + return; + } + } + } + if (NormalDNS2Compatible.IsNotEmpty()) + { + var obj2 = JsonUtils.Deserialize(NormalDNS2Compatible); + if (obj2 == null) + { + NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); + return; + } + } + if (TunDNS2Compatible.IsNotEmpty()) + { + var obj2 = JsonUtils.Deserialize(TunDNS2Compatible); + if (obj2 == null) + { + NoticeHandler.Instance.Enqueue(ResUI.FillCorrectDNSText); + return; + } + } + + var item1 = await AppHandler.Instance.GetDNSItem(ECoreType.Xray); + item1.Enabled = RayCustomDNSEnableCompatible; + item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible; + item1.DomainDNSAddress = DomainDNSAddressCompatible; + item1.UseSystemHosts = UseSystemHostsCompatible; + item1.NormalDNS = NormalDNSCompatible; + await ConfigHandler.SaveDNSItems(_config, item1); + + var item2 = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + item2.Enabled = RayCustomDNSEnableCompatible; + item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible; + item2.DomainDNSAddress = DomainDNSAddress2Compatible; + item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2Compatible)); + item2.TunDNS = JsonUtils.Serialize(JsonUtils.ParseJson(TunDNS2Compatible)); + await ConfigHandler.SaveDNSItems(_config, item2); + await ConfigHandler.SaveConfig(_config); if (_updateView != null) { diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 22d82895..36e20a87 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -219,6 +219,7 @@ public class MainWindowViewModel : MyReactiveObject _config.UiItem.ShowInTaskbar = true; await ConfigHandler.InitBuiltinRouting(_config); + await ConfigHandler.InitBuiltinDNS(_config); await ProfileExHandler.Instance.Init(); await CoreHandler.Instance.Init(_config, UpdateHandler); TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml index 9db53ea5..90700673 100644 --- a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml @@ -40,128 +40,136 @@ + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + + + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*"> + + + + + + + + + + + + + + + + + + + + + + + +