diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 2a4ba9a5..12089008 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -88,6 +88,7 @@ public class Global public const string SingboxLocalDNSTag = "local_local"; public const string SingboxHostsDNSTag = "hosts_dns"; public const string SingboxFakeDNSTag = "fake_dns"; + public const string SingboxEchDNSTag = "ech_dns"; public static readonly List IEProxyProtocols = [ diff --git a/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs b/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs index 499ad3de..c75b43ce 100644 --- a/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs +++ b/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs @@ -316,5 +316,72 @@ public class ProfileGroupItemManager return childAddresses; } + public static async Task> GetAllChildEchQuerySni(string indexId) + { + // include grand children + var childAddresses = new HashSet(); + if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null) + { + return childAddresses; + } + + if (groupItem.SubChildItems.IsNotEmpty()) + { + var subItems = await GetSubChildProfileItems(groupItem); + foreach (var childNode in subItems) + { + if (childNode.EchConfigList.IsNullOrEmpty()) + { + continue; + } + if (childNode.StreamSecurity == Global.StreamSecurity + && childNode.EchConfigList?.Contains("://") == true) + { + var idx = childNode.EchConfigList.IndexOf('+'); + childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni); + } + else + { + childAddresses.Add(childNode.Sni); + } + } + } + + var childIds = Utils.String2List(groupItem.ChildItems) ?? []; + + foreach (var childId in childIds) + { + var childNode = await AppManager.Instance.GetProfileItem(childId); + if (childNode == null) + { + continue; + } + + if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty()) + { + if (childNode.StreamSecurity == Global.StreamSecurity + && childNode.EchConfigList?.Contains("://") == true) + { + var idx = childNode.EchConfigList.IndexOf('+'); + childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni); + } + else + { + childAddresses.Add(childNode.Sni); + } + } + else if (childNode.ConfigType.IsGroupType()) + { + var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId); + foreach (var addr in subAddresses) + { + childAddresses.Add(addr); + } + } + } + + return childAddresses; + } + #endregion Helper } diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index cbf674fa..b4041772 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -182,6 +182,14 @@ public class Tls4Sbox public string? fragment_fallback_delay { get; set; } public bool? record_fragment { get; set; } public List? certificate { get; set; } + public Ech4Sbox? ech { get; set; } +} + +public class Ech4Sbox +{ + public bool enabled { get; set; } + public List? config { get; set; } + public string? query_server_name { get; set; } } public class Multiplex4Sbox diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index 71f25acd..397e9340 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -371,7 +371,7 @@ public partial class CoreConfigSingboxService(Config config) await GenRouting(singboxConfig); await GenExperimental(singboxConfig); - await GenDns(null, singboxConfig); + await GenDns(parentNode, singboxConfig); await ConvertGeo2Ruleset(singboxConfig); ret.Success = true; @@ -428,7 +428,7 @@ public partial class CoreConfigSingboxService(Config config) await GenRouting(singboxConfig); await GenExperimental(singboxConfig); - await GenDns(null, singboxConfig); + await GenDns(parentNode, singboxConfig); await ConvertGeo2Ruleset(singboxConfig); ret.Success = true; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index c273eb57..fa089140 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -13,8 +13,8 @@ public partial class CoreConfigSingboxService } var simpleDNSItem = _config.SimpleDNSItem; - await GenDnsServers(singboxConfig, simpleDNSItem); - await GenDnsRules(singboxConfig, simpleDNSItem); + await GenDnsServers(node, singboxConfig, simpleDNSItem); + await GenDnsRules(node, singboxConfig, simpleDNSItem); singboxConfig.dns ??= new Dns4Sbox(); singboxConfig.dns.independent_cache = true; @@ -52,7 +52,7 @@ public partial class CoreConfigSingboxService return 0; } - private async Task GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + private async Task GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) { var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem); @@ -133,6 +133,29 @@ public partial class CoreConfigSingboxService singboxConfig.dns.servers.Add(fakeip); } + // ech + var (_, dnsServer) = ParseEchParam(node?.EchConfigList); + if (dnsServer is not null) + { + dnsServer.tag = Global.SingboxEchDNSTag; + if (dnsServer.server is not null + && hostsDns.predefined.ContainsKey(dnsServer.server)) + { + dnsServer.domain_resolver = Global.SingboxHostsDNSTag; + } + else + { + dnsServer.domain_resolver = Global.SingboxLocalDNSTag; + } + singboxConfig.dns.servers.Add(dnsServer); + } + else if (node?.ConfigType.IsGroupType() == true) + { + var echDnsObject = JsonUtils.DeepCopy(directDns); + echDnsObject.tag = Global.SingboxEchDNSTag; + singboxConfig.dns.servers.Add(echDnsObject); + } + return await Task.FromResult(0); } @@ -146,7 +169,7 @@ public partial class CoreConfigSingboxService return await Task.FromResult(finalDns); } - private async Task GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + private async Task GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) { singboxConfig.dns ??= new Dns4Sbox(); singboxConfig.dns.rules ??= new List(); @@ -168,6 +191,31 @@ public partial class CoreConfigSingboxService } }); + var (ech, _) = ParseEchParam(node?.EchConfigList); + if (ech is not null) + { + var echDomain = ech.query_server_name ?? node?.Sni; + singboxConfig.dns.rules.Add(new() + { + query_type = new List { 64, 65 }, + server = Global.SingboxEchDNSTag, + domain = echDomain is not null ? new List { echDomain } : null, + }); + } + else if (node?.ConfigType.IsGroupType() == true) + { + var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList(); + if (queryServerNames.Count > 0) + { + singboxConfig.dns.rules.Add(new() + { + query_type = new List { 64, 65 }, + server = Global.SingboxEchDNSTag, + domain = queryServerNames, + }); + } + } + if (simpleDNSItem.BlockBindingQuery == true) { singboxConfig.dns.rules.Add(new() diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 50b1807f..e2c0d502 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -334,6 +334,11 @@ public partial class CoreConfigSingboxService }; tls.insecure = false; } + var (ech, _) = ParseEchParam(node.EchConfigList); + if (ech is not null) + { + tls.ech = ech; + } outbound.tls = tls; } catch (Exception ex) @@ -904,4 +909,31 @@ public partial class CoreConfigSingboxService } return await Task.FromResult(0); } + + private (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig) + { + if (echConfig.IsNullOrEmpty()) + { + return (null, null); + } + if (!echConfig.Contains("://")) + { + return (new Ech4Sbox() + { + enabled = true, + config = [$"-----BEGIN ECH CONFIGS-----\n" + + $"{echConfig}\n" + + $"-----END ECH CONFIGS-----"], + }, null); + } + var idx = echConfig.IndexOf('+'); + // NOTE: query_server_name, since sing-box 1.13.0 + //var queryServerName = idx > 0 ? echConfig[..idx] : null; + var echDnsServer = idx > 0 ? echConfig[(idx + 1)..] : echConfig; + return (new Ech4Sbox() + { + enabled = true, + query_server_name = null, + }, ParseDnsAddress(echDnsServer)); + } }