From 6174e93ad680427f5bc595eecc958dbfd232cfc1 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Wed, 7 Jan 2026 15:42:46 +0800 Subject: [PATCH] Add sing-box ech support --- v2rayN/ServiceLib/Global.cs | 1 + v2rayN/ServiceLib/Models/SingboxConfig.cs | 8 ++++ .../CoreConfig/Singbox/SingboxDnsService.cs | 42 +++++++++++++++++-- .../Singbox/SingboxOutboundService.cs | 20 +++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) 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/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/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index c273eb57..4d173372 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,27 @@ public partial class CoreConfigSingboxService singboxConfig.dns.servers.Add(fakeip); } + // ech + if (node?.StreamSecurity == Global.StreamSecurity + && node?.EchConfigList?.Contains("://") == true) + { + // example.com+https://1.1.1.1/dns-query + var idx = node.EchConfigList.IndexOf('+'); + var echDnsServer = idx > 0 ? node.EchConfigList[(idx + 1)..] : node.EchConfigList; + var echDnsObject = ParseDnsAddress(echDnsServer); + echDnsObject.tag = Global.SingboxEchDNSTag; + if (echDnsObject.server is not null + && hostsDns.predefined.ContainsKey(echDnsObject.server)) + { + echDnsObject.domain_resolver = Global.SingboxHostsDNSTag; + } + else + { + echDnsObject.domain_resolver = Global.SingboxLocalDNSTag; + } + singboxConfig.dns.servers.Add(echDnsObject); + } + return await Task.FromResult(0); } @@ -146,7 +167,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 +189,19 @@ public partial class CoreConfigSingboxService } }); + if (node?.StreamSecurity == Global.StreamSecurity + && node?.EchConfigList?.Contains("://") == true) + { + var idx = node.EchConfigList.IndexOf('+'); + var queryServerName = idx > 0 ? node.EchConfigList[..idx] : node.Sni; + singboxConfig.dns.rules.Add(new() + { + query_type = new List { 64, 65 }, + server = Global.SingboxEchDNSTag, + domain = [queryServerName], + }); + } + 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..9d11af7f 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -334,6 +334,26 @@ public partial class CoreConfigSingboxService }; tls.insecure = false; } + + if (!node.EchConfigList.IsNullOrEmpty()) + { + var ech = new Ech4Sbox() + { + enabled = true, + }; + if (node.EchConfigList.Contains("://")) + { + var idx = node.EchConfigList.IndexOf('+'); + ech.query_server_name = idx > 0 ? node.EchConfigList[..idx] : null; + } + else + { + ech.config = [$"-----BEGIN ECH CONFIGS-----\n" + + $"{node.EchConfigList}\n" + + $"-----END ECH CONFIGS-----"]; + } + tls.ech = ech; + } outbound.tls = tls; } catch (Exception ex)