Skip to content

Commit f3b8940

Browse files
authored
Add sing-box ech support (2dust#8603)
* Add sing-box ech support * Support group config type * Simplified code
1 parent 4562d4c commit f3b8940

6 files changed

Lines changed: 162 additions & 6 deletions

File tree

v2rayN/ServiceLib/Global.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public class Global
8888
public const string SingboxLocalDNSTag = "local_local";
8989
public const string SingboxHostsDNSTag = "hosts_dns";
9090
public const string SingboxFakeDNSTag = "fake_dns";
91+
public const string SingboxEchDNSTag = "ech_dns";
9192

9293
public static readonly List<string> IEProxyProtocols =
9394
[

v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,5 +316,72 @@ public static async Task<HashSet<string>> GetAllChildDomainAddresses(string inde
316316
return childAddresses;
317317
}
318318

319+
public static async Task<HashSet<string>> GetAllChildEchQuerySni(string indexId)
320+
{
321+
// include grand children
322+
var childAddresses = new HashSet<string>();
323+
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
324+
{
325+
return childAddresses;
326+
}
327+
328+
if (groupItem.SubChildItems.IsNotEmpty())
329+
{
330+
var subItems = await GetSubChildProfileItems(groupItem);
331+
foreach (var childNode in subItems)
332+
{
333+
if (childNode.EchConfigList.IsNullOrEmpty())
334+
{
335+
continue;
336+
}
337+
if (childNode.StreamSecurity == Global.StreamSecurity
338+
&& childNode.EchConfigList?.Contains("://") == true)
339+
{
340+
var idx = childNode.EchConfigList.IndexOf('+');
341+
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
342+
}
343+
else
344+
{
345+
childAddresses.Add(childNode.Sni);
346+
}
347+
}
348+
}
349+
350+
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
351+
352+
foreach (var childId in childIds)
353+
{
354+
var childNode = await AppManager.Instance.GetProfileItem(childId);
355+
if (childNode == null)
356+
{
357+
continue;
358+
}
359+
360+
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
361+
{
362+
if (childNode.StreamSecurity == Global.StreamSecurity
363+
&& childNode.EchConfigList?.Contains("://") == true)
364+
{
365+
var idx = childNode.EchConfigList.IndexOf('+');
366+
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
367+
}
368+
else
369+
{
370+
childAddresses.Add(childNode.Sni);
371+
}
372+
}
373+
else if (childNode.ConfigType.IsGroupType())
374+
{
375+
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
376+
foreach (var addr in subAddresses)
377+
{
378+
childAddresses.Add(addr);
379+
}
380+
}
381+
}
382+
383+
return childAddresses;
384+
}
385+
319386
#endregion Helper
320387
}

v2rayN/ServiceLib/Models/SingboxConfig.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ public class Tls4Sbox
182182
public string? fragment_fallback_delay { get; set; }
183183
public bool? record_fragment { get; set; }
184184
public List<string>? certificate { get; set; }
185+
public Ech4Sbox? ech { get; set; }
186+
}
187+
188+
public class Ech4Sbox
189+
{
190+
public bool enabled { get; set; }
191+
public List<string>? config { get; set; }
192+
public string? query_server_name { get; set; }
185193
}
186194

187195
public class Multiplex4Sbox

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parent
371371

372372
await GenRouting(singboxConfig);
373373
await GenExperimental(singboxConfig);
374-
await GenDns(null, singboxConfig);
374+
await GenDns(parentNode, singboxConfig);
375375
await ConvertGeo2Ruleset(singboxConfig);
376376

377377
ret.Success = true;
@@ -428,7 +428,7 @@ public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
428428

429429
await GenRouting(singboxConfig);
430430
await GenExperimental(singboxConfig);
431-
await GenDns(null, singboxConfig);
431+
await GenDns(parentNode, singboxConfig);
432432
await ConvertGeo2Ruleset(singboxConfig);
433433

434434
ret.Success = true;

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

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
1313
}
1414

1515
var simpleDNSItem = _config.SimpleDNSItem;
16-
await GenDnsServers(singboxConfig, simpleDNSItem);
17-
await GenDnsRules(singboxConfig, simpleDNSItem);
16+
await GenDnsServers(node, singboxConfig, simpleDNSItem);
17+
await GenDnsRules(node, singboxConfig, simpleDNSItem);
1818

1919
singboxConfig.dns ??= new Dns4Sbox();
2020
singboxConfig.dns.independent_cache = true;
@@ -52,7 +52,7 @@ private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
5252
return 0;
5353
}
5454

55-
private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
55+
private async Task<int> GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
5656
{
5757
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
5858

@@ -133,6 +133,29 @@ private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem
133133
singboxConfig.dns.servers.Add(fakeip);
134134
}
135135

136+
// ech
137+
var (_, dnsServer) = ParseEchParam(node?.EchConfigList);
138+
if (dnsServer is not null)
139+
{
140+
dnsServer.tag = Global.SingboxEchDNSTag;
141+
if (dnsServer.server is not null
142+
&& hostsDns.predefined.ContainsKey(dnsServer.server))
143+
{
144+
dnsServer.domain_resolver = Global.SingboxHostsDNSTag;
145+
}
146+
else
147+
{
148+
dnsServer.domain_resolver = Global.SingboxLocalDNSTag;
149+
}
150+
singboxConfig.dns.servers.Add(dnsServer);
151+
}
152+
else if (node?.ConfigType.IsGroupType() == true)
153+
{
154+
var echDnsObject = JsonUtils.DeepCopy(directDns);
155+
echDnsObject.tag = Global.SingboxEchDNSTag;
156+
singboxConfig.dns.servers.Add(echDnsObject);
157+
}
158+
136159
return await Task.FromResult(0);
137160
}
138161

@@ -146,7 +169,7 @@ private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, Simpl
146169
return await Task.FromResult(finalDns);
147170
}
148171

149-
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
172+
private async Task<int> GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
150173
{
151174
singboxConfig.dns ??= new Dns4Sbox();
152175
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
@@ -168,6 +191,31 @@ private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem s
168191
}
169192
});
170193

194+
var (ech, _) = ParseEchParam(node?.EchConfigList);
195+
if (ech is not null)
196+
{
197+
var echDomain = ech.query_server_name ?? node?.Sni;
198+
singboxConfig.dns.rules.Add(new()
199+
{
200+
query_type = new List<int> { 64, 65 },
201+
server = Global.SingboxEchDNSTag,
202+
domain = echDomain is not null ? new List<string> { echDomain } : null,
203+
});
204+
}
205+
else if (node?.ConfigType.IsGroupType() == true)
206+
{
207+
var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList();
208+
if (queryServerNames.Count > 0)
209+
{
210+
singboxConfig.dns.rules.Add(new()
211+
{
212+
query_type = new List<int> { 64, 65 },
213+
server = Global.SingboxEchDNSTag,
214+
domain = queryServerNames,
215+
});
216+
}
217+
}
218+
171219
if (simpleDNSItem.BlockBindingQuery == true)
172220
{
173221
singboxConfig.dns.rules.Add(new()

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,11 @@ private async Task<int> GenOutboundTls(ProfileItem node, Outbound4Sbox outbound)
334334
};
335335
tls.insecure = false;
336336
}
337+
var (ech, _) = ParseEchParam(node.EchConfigList);
338+
if (ech is not null)
339+
{
340+
tls.ech = ech;
341+
}
337342
outbound.tls = tls;
338343
}
339344
catch (Exception ex)
@@ -904,4 +909,31 @@ private async Task<int> AddRangeOutbounds(List<BaseServer4Sbox> servers, Singbox
904909
}
905910
return await Task.FromResult(0);
906911
}
912+
913+
private (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig)
914+
{
915+
if (echConfig.IsNullOrEmpty())
916+
{
917+
return (null, null);
918+
}
919+
if (!echConfig.Contains("://"))
920+
{
921+
return (new Ech4Sbox()
922+
{
923+
enabled = true,
924+
config = [$"-----BEGIN ECH CONFIGS-----\n" +
925+
$"{echConfig}\n" +
926+
$"-----END ECH CONFIGS-----"],
927+
}, null);
928+
}
929+
var idx = echConfig.IndexOf('+');
930+
// NOTE: query_server_name, since sing-box 1.13.0
931+
//var queryServerName = idx > 0 ? echConfig[..idx] : null;
932+
var echDnsServer = idx > 0 ? echConfig[(idx + 1)..] : echConfig;
933+
return (new Ech4Sbox()
934+
{
935+
enabled = true,
936+
query_server_name = null,
937+
}, ParseDnsAddress(echDnsServer));
938+
}
907939
}

0 commit comments

Comments
 (0)