dnscrypt-proxy使用随机端口;

支持多个回退DNS上游;
This commit is contained in:
老九 2021-08-27 00:51:24 +08:00
parent 5f425e79ba
commit bec32d2e35
12 changed files with 147 additions and 90 deletions

View File

@ -19,15 +19,9 @@ namespace FastGithub.Configuration
private ConcurrentDictionary<string, DomainConfig?> domainConfigCache; private ConcurrentDictionary<string, DomainConfig?> domainConfigCache;
/// <summary> /// <summary>
/// 未污染的dns /// 回退的dns
/// </summary>
public IPEndPoint PureDns { get; private set; }
/// <summary>
/// 速度快的dns
/// </summary> /// </summary>
public IPEndPoint FastDns { get; private set; } public IPEndPoint[] FallbackDns { get; set; }
/// <summary> /// <summary>
/// FastGithub配置 /// FastGithub配置
@ -41,8 +35,7 @@ namespace FastGithub.Configuration
this.logger = logger; this.logger = logger;
var opt = options.CurrentValue; var opt = options.CurrentValue;
this.PureDns = opt.PureDns.ToIPEndPoint(); this.FallbackDns = opt.FallbackDns.Select(item => item.ToIPEndPoint()).ToArray();
this.FastDns = opt.FastDns.ToIPEndPoint();
this.domainConfigs = ConvertDomainConfigs(opt.DomainConfigs); this.domainConfigs = ConvertDomainConfigs(opt.DomainConfigs);
this.domainConfigCache = new ConcurrentDictionary<string, DomainConfig?>(); this.domainConfigCache = new ConcurrentDictionary<string, DomainConfig?>();
@ -57,8 +50,7 @@ namespace FastGithub.Configuration
{ {
try try
{ {
this.PureDns = options.PureDns.ToIPEndPoint(); this.FallbackDns = options.FallbackDns.Select(item => item.ToIPEndPoint()).ToArray();
this.FastDns = options.FastDns.ToIPEndPoint();
this.domainConfigs = ConvertDomainConfigs(options.DomainConfigs); this.domainConfigs = ConvertDomainConfigs(options.DomainConfigs);
this.domainConfigCache = new ConcurrentDictionary<string, DomainConfig?>(); this.domainConfigCache = new ConcurrentDictionary<string, DomainConfig?>();
} }

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace FastGithub.Configuration namespace FastGithub.Configuration
{ {
@ -13,14 +14,9 @@ namespace FastGithub.Configuration
public ListenConfig Listen { get; set; } = new ListenConfig(); public ListenConfig Listen { get; set; } = new ListenConfig();
/// <summary> /// <summary>
/// 未污染的dns /// 回退的dns
/// </summary> /// </summary>
public DnsConfig PureDns { get; set; } = new DnsConfig { IPAddress = "127.0.0.1", Port = 5533 }; public DnsConfig[] FallbackDns { get; set; } = Array.Empty<DnsConfig>();
/// <summary>
/// 速度快的dns
/// </summary>
public DnsConfig FastDns { get; set; } = new DnsConfig { IPAddress = "114.114.114.114", Port = 53 };
/// <summary> /// <summary>
/// 代理的域名配置 /// 代理的域名配置

View File

@ -79,6 +79,45 @@ namespace FastGithub.Configuration
} }
} }
/// <summary>
/// 获取可用的随机端口
/// </summary>
/// <param name="addressFamily"></param>
/// <param name="min">最小值</param>
/// <returns></returns>
public static int GetAvailablePort(AddressFamily addressFamily, int min = 1024)
{
var hashSet = new HashSet<int>();
var tcpListeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
var udpListeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners();
foreach (var item in tcpListeners)
{
if (item.AddressFamily == addressFamily)
{
hashSet.Add(item.Port);
}
}
foreach (var item in udpListeners)
{
if (item.AddressFamily == addressFamily)
{
hashSet.Add(item.Port);
}
}
for (var port = min; port < ushort.MaxValue; port++)
{
if (hashSet.Contains(port) == false)
{
return port;
}
}
throw new FastGithubException("当前无可用的端口");
}
/// <summary> /// <summary>
/// 是否可以监听指定tcp端口 /// 是否可以监听指定tcp端口
/// </summary> /// </summary>

View File

@ -100,7 +100,7 @@ namespace FastGithub.Dns
/// <returns></returns> /// <returns></returns>
protected override Task ExecuteAsync(CancellationToken stoppingToken) protected override Task ExecuteAsync(CancellationToken stoppingToken)
{ {
return this.dnsOverUdpServer.ListenAsync(stoppingToken); return this.dnsOverUdpServer.HandleAsync(stoppingToken);
} }
/// <summary> /// <summary>

View File

@ -60,11 +60,11 @@ namespace FastGithub.Dns
} }
/// <summary> /// <summary>
/// 监听dns请求 /// 监听和处理dns请求
/// </summary> /// </summary>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task ListenAsync(CancellationToken cancellationToken) public async Task HandleAsync(CancellationToken cancellationToken)
{ {
var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (cancellationToken.IsCancellationRequested == false) while (cancellationToken.IsCancellationRequested == false)
@ -100,7 +100,7 @@ namespace FastGithub.Dns
} }
catch (Exception ex) catch (Exception ex)
{ {
this.logger.LogTrace($"处理DNS异常{ex.Message}"); this.logger.LogWarning($"处理DNS异常{ex.Message}");
} }
} }

View File

@ -2,7 +2,6 @@
using DNS.Protocol; using DNS.Protocol;
using DNS.Protocol.ResourceRecords; using DNS.Protocol.ResourceRecords;
using FastGithub.Configuration; using FastGithub.Configuration;
using Microsoft.Extensions.Logging;
using System; using System;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -58,8 +57,20 @@ namespace FastGithub.Dns
return response; return response;
} }
var fastResolver = new UdpRequestResolver(fastGithubConfig.FastDns); // 使用回退dns解析域名
return await fastResolver.Resolve(request, cancellationToken); foreach (var dns in this.fastGithubConfig.FallbackDns)
{
try
{
var resolver = new UdpRequestResolver(dns);
return await resolver.Resolve(request, cancellationToken);
}
catch (Exception)
{
}
}
throw new FastGithubException($"无法解析域名{domain}");
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using FastGithub.Configuration;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -29,10 +30,10 @@ namespace FastGithub.DomainResolve
/// <summary> /// <summary>
/// DnscryptProxy服务 /// DnscryptProxy服务
/// </summary> /// </summary>
/// <param name="endPoint">监听的节点</param> public DnscryptProxy()
public DnscryptProxy(IPEndPoint endPoint)
{ {
this.EndPoint = endPoint; var port = LocalMachine.GetAvailablePort(IPAddress.Loopback.AddressFamily, min: 5353);
this.EndPoint = new IPEndPoint(IPAddress.Loopback, port);
} }
/// <summary> /// <summary>

View File

@ -1,5 +1,4 @@
using FastGithub.Configuration; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading; using System.Threading;
@ -12,20 +11,19 @@ namespace FastGithub.DomainResolve
/// </summary> /// </summary>
sealed class DnscryptProxyHostedService : IHostedService sealed class DnscryptProxyHostedService : IHostedService
{ {
private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<DnscryptProxyHostedService> logger; private readonly ILogger<DnscryptProxyHostedService> logger;
private DnscryptProxy? dnscryptProxy; private readonly DnscryptProxy dnscryptProxy;
/// <summary> /// <summary>
/// DnscryptProxy后台服务 /// DnscryptProxy后台服务
/// </summary> /// </summary>
/// <param name="fastGithubConfig"></param> /// <param name="dnscryptProxy"></param>
/// <param name="logger"></param> /// <param name="logger"></param>
public DnscryptProxyHostedService( public DnscryptProxyHostedService(
FastGithubConfig fastGithubConfig, DnscryptProxy dnscryptProxy,
ILogger<DnscryptProxyHostedService> logger) ILogger<DnscryptProxyHostedService> logger)
{ {
this.fastGithubConfig = fastGithubConfig; this.dnscryptProxy = dnscryptProxy;
this.logger = logger; this.logger = logger;
} }
@ -36,19 +34,14 @@ namespace FastGithub.DomainResolve
/// <returns></returns> /// <returns></returns>
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
var pureDns = this.fastGithubConfig.PureDns; try
if (LocalMachine.ContainsIPAddress(pureDns.Address) == true)
{ {
this.dnscryptProxy = new DnscryptProxy(pureDns); await this.dnscryptProxy.StartAsync(cancellationToken);
try this.logger.LogInformation($"{this.dnscryptProxy}启动成功");
{ }
await this.dnscryptProxy.StartAsync(cancellationToken); catch (Exception ex)
this.logger.LogInformation($"{this.dnscryptProxy}启动成功"); {
} this.logger.LogWarning($"{this.dnscryptProxy}启动失败:{ex.Message}");
catch (Exception ex)
{
this.logger.LogWarning($"{this.dnscryptProxy}启动失败:{ex.Message}");
}
} }
} }
@ -59,19 +52,15 @@ namespace FastGithub.DomainResolve
/// <returns></returns> /// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
if (this.dnscryptProxy != null) try
{ {
try this.dnscryptProxy.Stop();
{ this.logger.LogInformation($"{this.dnscryptProxy}已停止");
this.dnscryptProxy.Stop(); }
this.logger.LogInformation($"{this.dnscryptProxy}已停止"); catch (Exception ex)
} {
catch (Exception ex) this.logger.LogWarning($"{this.dnscryptProxy}停止失败:{ex.Message}");
{
this.logger.LogWarning($"{this.dnscryptProxy}停止失败:{ex.Message}");
}
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@ -21,13 +21,14 @@ namespace FastGithub.DomainResolve
{ {
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private readonly FastGithubConfig fastGithubConfig; private readonly FastGithubConfig fastGithubConfig;
private readonly DnscryptProxy dnscryptProxy;
private readonly ILogger<DomainResolver> logger; private readonly ILogger<DomainResolver> logger;
private readonly TimeSpan lookupTimeout = TimeSpan.FromSeconds(2d); private readonly TimeSpan lookupTimeout = TimeSpan.FromSeconds(2d);
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(2d); private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(2d);
private readonly TimeSpan pureResolveCacheTimeSpan = TimeSpan.FromMinutes(5d); private readonly TimeSpan dnscryptExpiration = TimeSpan.FromMinutes(5d);
private readonly TimeSpan fastResolveCacheTimeSpan = TimeSpan.FromMinutes(1d); private readonly TimeSpan fallbackExpiration = TimeSpan.FromMinutes(1d);
private readonly TimeSpan loopbackResolveCacheTimeSpan = TimeSpan.FromSeconds(5d); private readonly TimeSpan loopbackExpiration = TimeSpan.FromSeconds(5d);
private readonly ConcurrentDictionary<DnsEndPoint, SemaphoreSlim> semaphoreSlims = new(); private readonly ConcurrentDictionary<DnsEndPoint, SemaphoreSlim> semaphoreSlims = new();
/// <summary> /// <summary>
@ -35,14 +36,17 @@ namespace FastGithub.DomainResolve
/// </summary> /// </summary>
/// <param name="memoryCache"></param> /// <param name="memoryCache"></param>
/// <param name="fastGithubConfig"></param> /// <param name="fastGithubConfig"></param>
/// <param name="dnscryptProxy"></param>
/// <param name="logger"></param> /// <param name="logger"></param>
public DomainResolver( public DomainResolver(
IMemoryCache memoryCache, IMemoryCache memoryCache,
FastGithubConfig fastGithubConfig, FastGithubConfig fastGithubConfig,
DnscryptProxy dnscryptProxy,
ILogger<DomainResolver> logger) ILogger<DomainResolver> logger)
{ {
this.memoryCache = memoryCache; this.memoryCache = memoryCache;
this.fastGithubConfig = fastGithubConfig; this.fastGithubConfig = fastGithubConfig;
this.dnscryptProxy = dnscryptProxy;
this.logger = logger; this.logger = logger;
} }
@ -69,65 +73,86 @@ namespace FastGithub.DomainResolve
/// <summary> /// <summary>
/// 查找ip /// 查找ip
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="target"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
private async Task<IPAddress> LookupAsync(DnsEndPoint endPoint, CancellationToken cancellationToken) private async Task<IPAddress> LookupAsync(DnsEndPoint target, CancellationToken cancellationToken)
{ {
if (this.memoryCache.TryGetValue<IPAddress>(endPoint, out var address)) if (this.memoryCache.TryGetValue<IPAddress>(target, out var address))
{ {
return address; return address;
} }
var expiration = this.pureResolveCacheTimeSpan; var expiration = this.dnscryptExpiration;
address = await this.LookupCoreAsync(this.fastGithubConfig.PureDns, endPoint, cancellationToken); address = await this.LookupCoreAsync(this.dnscryptProxy.EndPoint, target, cancellationToken);
if (address == null) if (address == null)
{ {
expiration = this.fastResolveCacheTimeSpan; expiration = this.fallbackExpiration;
address = await this.LookupCoreAsync(this.fastGithubConfig.FastDns, endPoint, cancellationToken); address = await this.FallbackLookupAsync(target, cancellationToken);
} }
if (address == null) if (address == null)
{ {
throw new FastGithubException($"当前解析不到{endPoint.Host}可用的ip请刷新重试"); throw new FastGithubException($"当前解析不到{target.Host}可用的ip请刷新重试");
} }
// 往往是被污染的dns // 往往是被污染的dns
if (address.Equals(IPAddress.Loopback) == true) if (address.Equals(IPAddress.Loopback) == true)
{ {
expiration = this.loopbackResolveCacheTimeSpan; expiration = this.loopbackExpiration;
} }
this.logger.LogInformation($"[{endPoint.Host}->{address}]"); this.logger.LogInformation($"[{target.Host}->{address}]");
this.memoryCache.Set(endPoint, address, expiration); this.memoryCache.Set(target, address, expiration);
return address; return address;
} }
/// <summary>
/// 回退查找ip
/// </summary>
/// <param name="target"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<IPAddress?> FallbackLookupAsync(DnsEndPoint target, CancellationToken cancellationToken)
{
foreach (var dns in this.fastGithubConfig.FallbackDns)
{
var address = await this.LookupCoreAsync(dns, target, cancellationToken);
if (address != null)
{
return address;
}
}
return default;
}
/// <summary> /// <summary>
/// 查找ip /// 查找ip
/// </summary> /// </summary>
/// <param name="dns"></param> /// <param name="dns"></param>
/// <param name="endPoint"></param> /// <param name="target"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
private async Task<IPAddress?> LookupCoreAsync(IPEndPoint dns, DnsEndPoint endPoint, CancellationToken cancellationToken) private async Task<IPAddress?> LookupCoreAsync(IPEndPoint dns, DnsEndPoint target, CancellationToken cancellationToken)
{ {
try try
{ {
var dnsClient = new DnsClient(dns); var dnsClient = new DnsClient(dns);
using var timeoutTokenSource = new CancellationTokenSource(this.lookupTimeout); using var timeoutTokenSource = new CancellationTokenSource(this.lookupTimeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
var addresses = await dnsClient.Lookup(endPoint.Host, RecordType.A, linkedTokenSource.Token); var addresses = await dnsClient.Lookup(target.Host, RecordType.A, linkedTokenSource.Token);
return await this.FindFastValueAsync(addresses, endPoint.Port, cancellationToken); return await this.FindFastValueAsync(addresses, target.Port, cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
this.logger.LogWarning($"dns({dns})无法解析{endPoint.Host}{ex.Message}"); this.logger.LogWarning($"dns({dns})无法解析{target.Host}{ex.Message}");
return default; return default;
} }
} }
/// <summary> /// <summary>
/// 获取最快的ip /// 获取最快的ip
/// </summary> /// </summary>
@ -152,6 +177,7 @@ namespace FastGithub.DomainResolve
return await fastTask; return await fastTask;
} }
/// <summary> /// <summary>
/// 验证远程节点是否可连接 /// 验证远程节点是否可连接
/// </summary> /// </summary>

View File

@ -12,9 +12,9 @@ namespace FastGithub.DomainResolve
/// <summary> /// <summary>
/// 解析域名 /// 解析域名
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="target"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task<IPAddress> ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default); Task<IPAddress> ResolveAsync(DnsEndPoint target, CancellationToken cancellationToken = default);
} }
} }

View File

@ -17,6 +17,7 @@ namespace FastGithub
public static IServiceCollection AddDomainResolve(this IServiceCollection services) public static IServiceCollection AddDomainResolve(this IServiceCollection services)
{ {
services.AddMemoryCache(); services.AddMemoryCache();
services.TryAddSingleton<DnscryptProxy>();
services.TryAddSingleton<IDomainResolver, DomainResolver>(); services.TryAddSingleton<IDomainResolver, DomainResolver>();
return services.AddHostedService<DnscryptProxyHostedService>(); return services.AddHostedService<DnscryptProxyHostedService>();
} }

View File

@ -5,14 +5,16 @@
"SshPort": 22, // ssh "SshPort": 22, // ssh
"DnsPort": 53 // dns "DnsPort": 53 // dns
}, },
"PureDns": { // DomainConfigs "FallbackDns": [ // DomainConfigs
"IPAddress": "127.0.0.1", {
"Port": 5533 // 5533dnscrypt-proxy "IPAddress": "114.114.114.114",
}, "Port": 53
"FastDns": { // DomainConfigs },
"IPAddress": "114.114.114.114", {
"Port": 53 "IPAddress": "8.8.8.8",
}, "Port": 53
}
],
"DomainConfigs": { "DomainConfigs": {
"*.x.y.z.com": { // *.0 "*.x.y.z.com": { // *.0
"TlsSni": false, // tlsSNI "TlsSni": false, // tlsSNI