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;
/// <summary>
/// 未污染的dns
/// 回退的dns
/// </summary>
public IPEndPoint PureDns { get; private set; }
/// <summary>
/// 速度快的dns
/// </summary>
public IPEndPoint FastDns { get; private set; }
public IPEndPoint[] FallbackDns { get; set; }
/// <summary>
/// FastGithub配置
@ -41,8 +35,7 @@ namespace FastGithub.Configuration
this.logger = logger;
var opt = options.CurrentValue;
this.PureDns = opt.PureDns.ToIPEndPoint();
this.FastDns = opt.FastDns.ToIPEndPoint();
this.FallbackDns = opt.FallbackDns.Select(item => item.ToIPEndPoint()).ToArray();
this.domainConfigs = ConvertDomainConfigs(opt.DomainConfigs);
this.domainConfigCache = new ConcurrentDictionary<string, DomainConfig?>();
@ -57,8 +50,7 @@ namespace FastGithub.Configuration
{
try
{
this.PureDns = options.PureDns.ToIPEndPoint();
this.FastDns = options.FastDns.ToIPEndPoint();
this.FallbackDns = options.FallbackDns.Select(item => item.ToIPEndPoint()).ToArray();
this.domainConfigs = ConvertDomainConfigs(options.DomainConfigs);
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
{
@ -13,14 +14,9 @@ namespace FastGithub.Configuration
public ListenConfig Listen { get; set; } = new ListenConfig();
/// <summary>
/// 未污染的dns
/// 回退的dns
/// </summary>
public DnsConfig PureDns { get; set; } = new DnsConfig { IPAddress = "127.0.0.1", Port = 5533 };
/// <summary>
/// 速度快的dns
/// </summary>
public DnsConfig FastDns { get; set; } = new DnsConfig { IPAddress = "114.114.114.114", Port = 53 };
public DnsConfig[] FallbackDns { get; set; } = Array.Empty<DnsConfig>();
/// <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>
/// 是否可以监听指定tcp端口
/// </summary>

View File

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

View File

@ -60,11 +60,11 @@ namespace FastGithub.Dns
}
/// <summary>
/// 监听dns请求
/// 监听和处理dns请求
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task ListenAsync(CancellationToken cancellationToken)
public async Task HandleAsync(CancellationToken cancellationToken)
{
var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (cancellationToken.IsCancellationRequested == false)
@ -100,7 +100,7 @@ namespace FastGithub.Dns
}
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.ResourceRecords;
using FastGithub.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Net;
@ -58,8 +57,20 @@ namespace FastGithub.Dns
return response;
}
var fastResolver = new UdpRequestResolver(fastGithubConfig.FastDns);
return await fastResolver.Resolve(request, cancellationToken);
// 使用回退dns解析域名
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.IO;
using System.Linq;
@ -29,10 +30,10 @@ namespace FastGithub.DomainResolve
/// <summary>
/// DnscryptProxy服务
/// </summary>
/// <param name="endPoint">监听的节点</param>
public DnscryptProxy(IPEndPoint endPoint)
public DnscryptProxy()
{
this.EndPoint = endPoint;
var port = LocalMachine.GetAvailablePort(IPAddress.Loopback.AddressFamily, min: 5353);
this.EndPoint = new IPEndPoint(IPAddress.Loopback, port);
}
/// <summary>

View File

@ -1,5 +1,4 @@
using FastGithub.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
@ -12,20 +11,19 @@ namespace FastGithub.DomainResolve
/// </summary>
sealed class DnscryptProxyHostedService : IHostedService
{
private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<DnscryptProxyHostedService> logger;
private DnscryptProxy? dnscryptProxy;
private readonly DnscryptProxy dnscryptProxy;
/// <summary>
/// DnscryptProxy后台服务
/// </summary>
/// <param name="fastGithubConfig"></param>
/// <param name="dnscryptProxy"></param>
/// <param name="logger"></param>
public DnscryptProxyHostedService(
FastGithubConfig fastGithubConfig,
DnscryptProxy dnscryptProxy,
ILogger<DnscryptProxyHostedService> logger)
{
this.fastGithubConfig = fastGithubConfig;
this.dnscryptProxy = dnscryptProxy;
this.logger = logger;
}
@ -36,10 +34,6 @@ namespace FastGithub.DomainResolve
/// <returns></returns>
public async Task StartAsync(CancellationToken cancellationToken)
{
var pureDns = this.fastGithubConfig.PureDns;
if (LocalMachine.ContainsIPAddress(pureDns.Address) == true)
{
this.dnscryptProxy = new DnscryptProxy(pureDns);
try
{
await this.dnscryptProxy.StartAsync(cancellationToken);
@ -50,7 +44,6 @@ namespace FastGithub.DomainResolve
this.logger.LogWarning($"{this.dnscryptProxy}启动失败:{ex.Message}");
}
}
}
/// <summary>
/// 停止dnscrypt-proxy
@ -58,8 +51,6 @@ namespace FastGithub.DomainResolve
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken)
{
if (this.dnscryptProxy != null)
{
try
{
@ -70,8 +61,6 @@ namespace FastGithub.DomainResolve
{
this.logger.LogWarning($"{this.dnscryptProxy}停止失败:{ex.Message}");
}
}
return Task.CompletedTask;
}
}

View File

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

View File

@ -12,9 +12,9 @@ namespace FastGithub.DomainResolve
/// <summary>
/// 解析域名
/// </summary>
/// <param name="endPoint"></param>
/// <param name="target"></param>
/// <param name="cancellationToken"></param>
/// <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)
{
services.AddMemoryCache();
services.TryAddSingleton<DnscryptProxy>();
services.TryAddSingleton<IDomainResolver, DomainResolver>();
return services.AddHostedService<DnscryptProxyHostedService>();
}

View File

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