소스 검색

全局ping

陈国伟 3 년 전
부모
커밋
e212fdc5dc

+ 66 - 2
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<DnsClient> logger;
 
+        private readonly ConcurrentDictionary<string, IPAddressCollection> domainIPAddressCollection = new();
+
         private readonly ConcurrentDictionary<string, SemaphoreSlim> 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
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         public async IAsyncEnumerable<IPAddress> 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;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 对所有域名所有IP进行ping测试
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        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();
+            }
+        }
+
+        /// <summary>
+        /// 尝试获取域名下已经过ping排序的IP地址
+        /// </summary>
+        /// <param name="domain"></param>
+        /// <param name="addresses"></param>
+        /// <returns></returns>
+        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;
+        }
+
+        /// <summary>
+        /// 解析域名
+        /// </summary>
+        /// <param name="domain">域名</param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        private async IAsyncEnumerable<IPAddress> ResolveCoreAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
         {
             var hashSet = new HashSet<IPAddress>();
             foreach (var dns in this.GetDnsServers())
@@ -75,7 +141,6 @@ namespace FastGithub.DomainResolve
             }
         }
 
-
         /// <summary>
         /// 获取dns服务
         /// </summary>
@@ -184,7 +249,6 @@ namespace FastGithub.DomainResolve
                 timeToLive = this.defaultEmptyTtl;
             }
 
-            this.logger.LogWarning($"{domain} [{timeToLive}]");
             return new LookupResult(addresses, timeToLive);
         }
 

+ 15 - 3
FastGithub.DomainResolve/DnscryptProxyHostedService.cs → 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
     /// <summary>
     /// 域名解析后台服务
     /// </summary>
-    sealed class DnscryptProxyHostedService : BackgroundService
+    sealed class DomainResolveHostedService : BackgroundService
     {
         private readonly DnscryptProxy dnscryptProxy;
+        private readonly DnsClient dnsClient;
+        private readonly TimeSpan speedTestTimeSpan = TimeSpan.FromMinutes(2d);
 
         /// <summary>
         /// 域名解析后台服务
         /// </summary>
-        /// <param name="dnscryptProxy"></param> 
-        public DnscryptProxyHostedService(DnscryptProxy dnscryptProxy)
+        /// <param name="dnscryptProxy"></param>
+        /// <param name="dnsClient"></param>
+        public DomainResolveHostedService(
+            DnscryptProxy dnscryptProxy,
+            DnsClient dnsClient)
         {
             this.dnscryptProxy = dnscryptProxy;
+            this.dnsClient = dnsClient;
         }
 
         /// <summary>
@@ -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);
+            }
         }
 
         /// <summary>

+ 139 - 0
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
+{
+    /// <summary>
+    /// IPAddress集合
+    /// </summary>
+    [DebuggerDisplay("Count = {Count}")]
+    sealed class IPAddressCollection
+    {
+        private readonly object syncRoot = new();
+        private readonly HashSet<IPAddressItem> hashSet = new();
+
+        /// <summary>
+        /// 获取元素数量
+        /// </summary>
+        public int Count => this.hashSet.Count;
+
+        /// <summary>
+        /// 添加元素
+        /// </summary>
+        /// <param name="address"></param>
+        /// <returns></returns>
+        public bool Add(IPAddress address)
+        {
+            lock (this.syncRoot)
+            {
+                return this.hashSet.Add(new IPAddressItem(address));
+            }
+        }
+
+        /// <summary>
+        /// 转后为数组
+        /// </summary>
+        /// <returns></returns>
+        public IPAddress[] ToArray()
+        {
+            return this.ToItemArray().OrderBy(item => item.PingElapsed).Select(item => item.Address).ToArray();
+        }
+
+        /// <summary>
+        /// Ping所有IP
+        /// </summary>
+        /// <returns></returns>
+        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);
+        }
+
+        /// <summary>
+        /// 转换为数组
+        /// </summary>
+        /// <returns></returns>
+        private IPAddressItem[] ToItemArray()
+        {
+            lock (this.syncRoot)
+            {
+                return this.hashSet.ToArray();
+            }
+        }
+
+        /// <summary>
+        /// IP地址项
+        /// </summary>
+        [DebuggerDisplay("Address = {Address}, PingElapsed = {PingElapsed}")]
+        private class IPAddressItem : IEquatable<IPAddressItem>
+        {
+            /// <summary>
+            /// 地址
+            /// </summary>
+            public IPAddress Address { get; }
+
+            /// <summary>
+            /// Ping耗时
+            /// </summary>
+            public TimeSpan PingElapsed { get; private set; } = TimeSpan.MaxValue;
+
+            /// <summary>
+            /// IP地址项
+            /// </summary>
+            /// <param name="address"></param>
+            public IPAddressItem(IPAddress address)
+            {
+                this.Address = address;
+            }
+
+            /// <summary>
+            /// 发起ping请求
+            /// </summary>
+            /// <returns></returns>
+            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();
+            }
+        }
+    }
+}

+ 1 - 1
FastGithub.DomainResolve/ServiceCollectionExtensions.cs

@@ -19,7 +19,7 @@ namespace FastGithub
             services.TryAddSingleton<DnsClient>();
             services.TryAddSingleton<DnscryptProxy>(); 
             services.TryAddSingleton<IDomainResolver, DomainResolver>();
-            services.AddHostedService<DnscryptProxyHostedService>();
+            services.AddHostedService<DomainResolveHostedService>();
             return services;
         }
     }