diff --git a/FastGithub.DomainResolve/DnsClient.cs b/FastGithub.DomainResolve/DnsClient.cs index 29e4996..fe3b175 100644 --- a/FastGithub.DomainResolve/DnsClient.cs +++ b/FastGithub.DomainResolve/DnsClient.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Options; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Net.NetworkInformation; @@ -30,6 +31,8 @@ namespace FastGithub.DomainResolve private readonly FastGithubConfig fastGithubConfig; private readonly ILogger logger; + private readonly ConcurrentDictionary domainIPAddressCollection = new(); + private readonly ConcurrentDictionary semaphoreSlims = new(); private readonly IMemoryCache dnsCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly TimeSpan defaultEmptyTtl = TimeSpan.FromSeconds(30d); @@ -60,6 +63,69 @@ namespace FastGithub.DomainResolve /// /// public async IAsyncEnumerable ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (this.TryGetPingedIPAddresses(domain, out var addresses)) + { + foreach (var address in addresses) + { + yield return address; + } + } + else + { + this.domainIPAddressCollection.TryAdd(domain, new IPAddressCollection()); + await foreach (var adddress in this.ResolveCoreAsync(domain, cancellationToken)) + { + yield return adddress; + } + } + } + + /// + /// 对所有域名所有IP进行ping测试 + /// + /// + /// + public async Task PingAllDomainsAsync(CancellationToken cancellationToken) + { + foreach (var keyValue in this.domainIPAddressCollection) + { + var domain = keyValue.Key; + var collection = keyValue.Value; + + await foreach (var address in this.ResolveCoreAsync(domain, cancellationToken)) + { + collection.Add(address); + } + await collection.PingAllAsync(); + } + } + + /// + /// 尝试获取域名下已经过ping排序的IP地址 + /// + /// + /// + /// + private bool TryGetPingedIPAddresses(string domain, [MaybeNullWhen(false)] out IPAddress[] addresses) + { + if (this.domainIPAddressCollection.TryGetValue(domain, out var collection) && collection.Count > 0) + { + addresses = collection.ToArray(); + return true; + } + + addresses = default; + return false; + } + + /// + /// 解析域名 + /// + /// 域名 + /// + /// + private async IAsyncEnumerable ResolveCoreAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken) { var hashSet = new HashSet(); foreach (var dns in this.GetDnsServers()) @@ -75,7 +141,6 @@ namespace FastGithub.DomainResolve } } - /// /// 获取dns服务 /// @@ -184,7 +249,6 @@ namespace FastGithub.DomainResolve timeToLive = this.defaultEmptyTtl; } - this.logger.LogWarning($"{domain} [{timeToLive}]"); return new LookupResult(addresses, timeToLive); } diff --git a/FastGithub.DomainResolve/DnscryptProxyHostedService.cs b/FastGithub.DomainResolve/DomainResolveHostedService.cs similarity index 60% rename from FastGithub.DomainResolve/DnscryptProxyHostedService.cs rename to FastGithub.DomainResolve/DomainResolveHostedService.cs index 10ae281..434298d 100644 --- a/FastGithub.DomainResolve/DnscryptProxyHostedService.cs +++ b/FastGithub.DomainResolve/DomainResolveHostedService.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using System; using System.Threading; using System.Threading.Tasks; @@ -7,17 +8,23 @@ namespace FastGithub.DomainResolve /// /// 域名解析后台服务 /// - sealed class DnscryptProxyHostedService : BackgroundService + sealed class DomainResolveHostedService : BackgroundService { private readonly DnscryptProxy dnscryptProxy; + private readonly DnsClient dnsClient; + private readonly TimeSpan speedTestTimeSpan = TimeSpan.FromMinutes(2d); /// /// 域名解析后台服务 /// - /// - public DnscryptProxyHostedService(DnscryptProxy dnscryptProxy) + /// + /// + public DomainResolveHostedService( + DnscryptProxy dnscryptProxy, + DnsClient dnsClient) { this.dnscryptProxy = dnscryptProxy; + this.dnsClient = dnsClient; } /// @@ -28,6 +35,11 @@ namespace FastGithub.DomainResolve protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await this.dnscryptProxy.StartAsync(stoppingToken); + while (stoppingToken.IsCancellationRequested == false) + { + await this.dnsClient.PingAllDomainsAsync(stoppingToken); + await Task.Delay(this.speedTestTimeSpan, stoppingToken); + } } /// diff --git a/FastGithub.DomainResolve/IPAddressCollection.cs b/FastGithub.DomainResolve/IPAddressCollection.cs new file mode 100644 index 0000000..56ce21f --- /dev/null +++ b/FastGithub.DomainResolve/IPAddressCollection.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Threading.Tasks; + +namespace FastGithub.DomainResolve +{ + /// + /// IPAddress集合 + /// + [DebuggerDisplay("Count = {Count}")] + sealed class IPAddressCollection + { + private readonly object syncRoot = new(); + private readonly HashSet hashSet = new(); + + /// + /// 获取元素数量 + /// + public int Count => this.hashSet.Count; + + /// + /// 添加元素 + /// + /// + /// + public bool Add(IPAddress address) + { + lock (this.syncRoot) + { + return this.hashSet.Add(new IPAddressItem(address)); + } + } + + /// + /// 转后为数组 + /// + /// + public IPAddress[] ToArray() + { + return this.ToItemArray().OrderBy(item => item.PingElapsed).Select(item => item.Address).ToArray(); + } + + /// + /// Ping所有IP + /// + /// + public Task PingAllAsync() + { + var items = this.ToItemArray(); + if (items.Length == 0) + { + return Task.CompletedTask; + } + if (items.Length == 1) + { + return items[0].PingAsync(); + } + var tasks = items.Select(item => item.PingAsync()); + return Task.WhenAll(tasks); + } + + /// + /// 转换为数组 + /// + /// + private IPAddressItem[] ToItemArray() + { + lock (this.syncRoot) + { + return this.hashSet.ToArray(); + } + } + + /// + /// IP地址项 + /// + [DebuggerDisplay("Address = {Address}, PingElapsed = {PingElapsed}")] + private class IPAddressItem : IEquatable + { + /// + /// 地址 + /// + public IPAddress Address { get; } + + /// + /// Ping耗时 + /// + public TimeSpan PingElapsed { get; private set; } = TimeSpan.MaxValue; + + /// + /// IP地址项 + /// + /// + public IPAddressItem(IPAddress address) + { + this.Address = address; + } + + /// + /// 发起ping请求 + /// + /// + public async Task PingAsync() + { + try + { + using var ping = new Ping(); + var reply = await ping.SendPingAsync(this.Address); + this.PingElapsed = reply.Status == IPStatus.Success + ? TimeSpan.FromMilliseconds(reply.RoundtripTime) + : TimeSpan.MaxValue; + } + catch (Exception) + { + this.PingElapsed = 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(); + } + } + } +} diff --git a/FastGithub.DomainResolve/ServiceCollectionExtensions.cs b/FastGithub.DomainResolve/ServiceCollectionExtensions.cs index 68e1583..e43006d 100644 --- a/FastGithub.DomainResolve/ServiceCollectionExtensions.cs +++ b/FastGithub.DomainResolve/ServiceCollectionExtensions.cs @@ -19,7 +19,7 @@ namespace FastGithub services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.AddHostedService(); + services.AddHostedService(); return services; } }