强化dns客户端

This commit is contained in:
陈国伟 2021-09-28 08:47:06 +08:00
parent f72f6dea78
commit ad51a0a572
4 changed files with 158 additions and 46 deletions

View File

@ -2,13 +2,16 @@
using DNS.Client.RequestResolver;
using DNS.Protocol;
using DNS.Protocol.ResourceRecords;
using FastGithub.Configuration;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@ -21,6 +24,9 @@ namespace FastGithub.DomainResolve
{
private const int DNS_PORT = 53;
private const string LOCALHOST = "localhost";
private readonly DnscryptProxy dnscryptProxy;
private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<DnsClient> logger;
private readonly ConcurrentDictionary<string, SemaphoreSlim> semaphoreSlims = new();
@ -30,13 +36,60 @@ namespace FastGithub.DomainResolve
/// <summary>
/// DNS客户端
/// </summary>
/// </summary>
/// <param name="dnscryptProxy"></param>
/// <param name="fastGithubConfig"></param>
/// <param name="logger"></param>
public DnsClient(ILogger<DnsClient> logger)
public DnsClient(
DnscryptProxy dnscryptProxy,
FastGithubConfig fastGithubConfig,
ILogger<DnsClient> logger)
{
this.dnscryptProxy = dnscryptProxy;
this.fastGithubConfig = fastGithubConfig;
this.logger = logger;
}
/// <summary>
/// 解析域名
/// </summary>
/// <param name="domain">域名</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<IPAddress> ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var hashSet = new HashSet<IPAddress>();
foreach (var dns in this.GetDnsServers())
{
foreach (var address in await this.LookupAsync(dns, domain, cancellationToken))
{
if (hashSet.Add(address) == true)
{
yield return address;
}
}
}
}
/// <summary>
/// 获取dns服务
/// </summary>
/// <returns></returns>
private IEnumerable<IPEndPoint> GetDnsServers()
{
var cryptDns = this.dnscryptProxy.LocalEndPoint;
if (cryptDns != null)
{
yield return cryptDns;
yield return cryptDns;
}
foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
{
yield return fallbackDns;
}
}
/// <summary>
/// 解析域名
/// </summary>
@ -44,7 +97,7 @@ namespace FastGithub.DomainResolve
/// <param name="domain"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IPAddress[]> LookupAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
private async Task<IPAddress[]> LookupAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
{
var key = $"{dns}:{domain}";
var semaphore = this.semaphoreSlims.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));

View File

@ -1,7 +1,6 @@
using FastGithub.Configuration;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@ -12,23 +11,14 @@ namespace FastGithub.DomainResolve
/// </summary>
sealed class DomainResolver : IDomainResolver
{
private readonly DnscryptProxy dnscryptProxy;
private readonly FastGithubConfig fastGithubConfig;
private readonly DnsClient dnsClient;
/// <summary>
/// 域名解析器
/// </summary>
/// <param name="dnscryptProxy"></param>
/// <param name="fastGithubConfig"></param>
/// </summary>
/// <param name="dnsClient"></param>
public DomainResolver(
DnscryptProxy dnscryptProxy,
FastGithubConfig fastGithubConfig,
DnsClient dnsClient)
public DomainResolver(DnsClient dnsClient)
{
this.dnscryptProxy = dnscryptProxy;
this.fastGithubConfig = fastGithubConfig;
this.dnsClient = dnsClient;
}
@ -53,38 +43,9 @@ namespace FastGithub.DomainResolve
/// <param name="domain">域名</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<IPAddress> ResolveAllAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
public IAsyncEnumerable<IPAddress> ResolveAllAsync(string domain, CancellationToken cancellationToken)
{
var hashSet = new HashSet<IPAddress>();
foreach (var dns in this.GetDnsServers())
{
foreach (var address in await this.dnsClient.LookupAsync(dns, domain, cancellationToken))
{
if (hashSet.Add(address) == true)
{
yield return address;
}
}
}
}
/// <summary>
/// 获取dns服务
/// </summary>
/// <returns></returns>
private IEnumerable<IPEndPoint> GetDnsServers()
{
var cryptDns = this.dnscryptProxy.LocalEndPoint;
if (cryptDns != null)
{
yield return cryptDns;
yield return cryptDns;
}
foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
{
yield return fallbackDns;
}
return this.dnsClient.ResolveAsync(domain, cancellationToken);
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace FastGithub.DomainResolve
{
sealed class IPAddressItem : IEquatable<IPAddressItem>
{
public IPAddress Address { get; }
public TimeSpan Elapsed { get; private set; } = TimeSpan.MaxValue;
public IPAddressItem(IPAddress address)
{
this.Address = address;
}
public async Task TestSpeedAsync()
{
try
{
using var ping = new Ping();
var reply = await ping.SendPingAsync(this.Address);
this.Elapsed = reply.Status == IPStatus.Success
? TimeSpan.FromMilliseconds(reply.RoundtripTime)
: TimeSpan.MaxValue;
}
catch (Exception)
{
this.Elapsed = TimeSpan.MaxValue;
}
}
public bool Equals(IPAddressItem? other)
{
return other != null && other.Address.Equals(this.Address);
}
public override bool Equals(object? obj)
{
return obj is IPAddressItem other && this.Equals(other);
}
public override int GetHashCode()
{
return this.Address.GetHashCode();
}
}
}

View File

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FastGithub.DomainResolve
{
sealed class IPAddressItemHashSet
{
private readonly object syncRoot = new();
private readonly HashSet<IPAddressItem> hashSet = new();
public int Count => this.hashSet.Count;
public bool Add(IPAddressItem item)
{
lock (this.syncRoot)
{
return this.hashSet.Add(item);
}
}
public void AddRange(IEnumerable<IPAddressItem> items)
{
lock (this.syncRoot)
{
foreach (var item in items)
{
this.hashSet.Add(item);
}
}
}
public IPAddressItem[] ToArray()
{
lock (this.syncRoot)
{
return this.hashSet.ToArray();
}
}
public Task TestSpeedAsync()
{
var tasks = this.ToArray().Select(item => item.TestSpeedAsync());
return Task.WhenAll(tasks);
}
}
}