陈国伟 3 лет назад
Родитель
Сommit
9516a22c26

+ 53 - 19
FastGithub.DomainResolve/DnsClient.cs

@@ -2,6 +2,9 @@
 using DNS.Client.RequestResolver;
 using DNS.Protocol;
 using DNS.Protocol.ResourceRecords;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using System;
 using System.Linq;
 using System.Net;
@@ -15,48 +18,79 @@ namespace FastGithub.DomainResolve
     /// </summary>
     sealed class DnsClient
     {
-        private readonly IPEndPoint dns;
-        private readonly IRequestResolver resolver;
-        private readonly int timeout = (int)TimeSpan.FromSeconds(2d).TotalMilliseconds;
+        private readonly ILogger<DnsClient> logger;
+
+        private readonly int resolveTimeout = (int)TimeSpan.FromSeconds(2d).TotalMilliseconds;
+        private readonly IMemoryCache dnsCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
+        private readonly TimeSpan dnsExpiration = TimeSpan.FromMinutes(2d);
 
         /// <summary>
         /// DNS客户端
+        /// </summary> 
+        /// <param name="logger"></param>
+        public DnsClient(ILogger<DnsClient> logger)
+        {
+            this.logger = logger;
+        }
+
+        /// <summary>
+        /// 解析域名
         /// </summary>
         /// <param name="dns"></param>
-        public DnsClient(IPEndPoint dns)
+        /// <param name="domain"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IPAddress[]> LookupAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
         {
-            this.dns = dns;
-            this.resolver = dns.Port == 53
-                ? new TcpRequestResolver(dns)
-                : new UdpRequestResolver(dns, new TcpRequestResolver(dns), this.timeout);
+            var key = $"{dns}:{domain}";
+            if (this.dnsCache.TryGetValue<IPAddress[]>(key, out var value))
+            {
+                return value;
+            }
+
+            try
+            {
+                value = await this.LookupCoreAsync(dns, domain, cancellationToken);
+                this.dnsCache.Set(key, value, this.dnsExpiration);
+
+                var items = string.Join(", ", value.Select(item => item.ToString()));
+                this.logger.LogInformation($"{dns}:{domain}->[{items}]");
+                return value;
+            }
+            catch (Exception ex)
+            {
+                this.logger.LogWarning($"{dns}无法解析{domain}:{ex.Message}");
+                return Array.Empty<IPAddress>();
+            }
         }
 
         /// <summary>
         /// 解析域名
         /// </summary>
+        /// <param name="dns"></param>
         /// <param name="domain"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task<IPAddress[]> LookupAsync(string domain, CancellationToken cancellationToken = default)
+        private async Task<IPAddress[]> LookupCoreAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
         {
+            if (domain == "localhost")
+            {
+                return new[] { IPAddress.Loopback };
+            }
+
+            var resolver = dns.Port == 53
+                ? (IRequestResolver)new TcpRequestResolver(dns)
+                : new UdpRequestResolver(dns, new TcpRequestResolver(dns), this.resolveTimeout);
+
             var request = new Request
             {
                 RecursionDesired = true,
                 OperationCode = OperationCode.Query
             };
             request.Questions.Add(new Question(new Domain(domain), RecordType.A));
-            var clientRequest = new ClientRequest(this.resolver, request);
+            var clientRequest = new ClientRequest(resolver, request);
             var response = await clientRequest.Resolve(cancellationToken);
             return response.AnswerRecords.OfType<IPAddressResourceRecord>().Select(item => item.IPAddress).ToArray();
         }
-
-        /// <summary>
-        /// 转换为文本
-        /// </summary>
-        /// <returns></returns>
-        public override string ToString()
-        {
-            return $"dns://{this.dns}";
-        }
     }
 }

+ 20 - 48
FastGithub.DomainResolve/DomainResolver.cs

@@ -1,11 +1,9 @@
 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.Net.Sockets;
 using System.Runtime.CompilerServices;
@@ -21,7 +19,7 @@ namespace FastGithub.DomainResolve
     {
         private readonly DnscryptProxy dnscryptProxy;
         private readonly FastGithubConfig fastGithubConfig;
-        private readonly ILogger<DomainResolver> logger;
+        private readonly DnsClient dnsClient;
 
         private readonly ConcurrentDictionary<IPEndPoint, SemaphoreSlim> semaphoreSlims = new();
         private readonly IMemoryCache ipEndPointAvailableCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
@@ -33,33 +31,33 @@ namespace FastGithub.DomainResolve
         /// </summary>
         /// <param name="dnscryptProxy"></param>
         /// <param name="fastGithubConfig"></param>
-        /// <param name="logger"></param>
+        /// <param name="dnsClient"></param>
         public DomainResolver(
             DnscryptProxy dnscryptProxy,
             FastGithubConfig fastGithubConfig,
-            ILogger<DomainResolver> logger)
+            DnsClient dnsClient)
         {
             this.dnscryptProxy = dnscryptProxy;
             this.fastGithubConfig = fastGithubConfig;
-            this.logger = logger;
+            this.dnsClient = dnsClient;
         }
 
         /// <summary>
-        /// 解析域名
+        /// 解析可用的ip
         /// </summary>
-        /// <param name="domain"></param>
+        /// <param name="endPoint">远程节点</param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task<IPAddress> ResolveAsync(DnsEndPoint domain, CancellationToken cancellationToken)
+        public async Task<IPAddress> ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default)
         {
-            await foreach (var address in this.ResolveAsync(domain.Host, cancellationToken))
+            await foreach (var address in this.ResolveAsync(endPoint.Host, cancellationToken))
             {
-                if (await this.IsAvailableAsync(new IPEndPoint(address, domain.Port), cancellationToken))
+                if (await this.IsAvailableAsync(new IPEndPoint(address, endPoint.Port), cancellationToken))
                 {
                     return address;
                 }
             }
-            throw new FastGithubException($"解析不到{domain.Host}可用的IP");
+            throw new FastGithubException($"解析不到{endPoint.Host}可用的IP");
         }
 
         /// <summary>
@@ -112,30 +110,10 @@ namespace FastGithub.DomainResolve
         /// <returns></returns>
         public async IAsyncEnumerable<IPAddress> ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
         {
-            if (domain == "localhost")
-            {
-                yield return IPAddress.Loopback;
-                yield break;
-            }
-
             var hashSet = new HashSet<IPAddress>();
-            var cryptDns = this.dnscryptProxy.LocalEndPoint;
-            if (cryptDns != null)
-            {
-                var dnsClient = new DnsClient(cryptDns);
-                foreach (var address in await this.LookupAsync(dnsClient, domain, cancellationToken))
-                {
-                    if (hashSet.Add(address) == true)
-                    {
-                        yield return address;
-                    }
-                }
-            }
-
-            foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
+            foreach (var dns in this.GetDnsServers())
             {
-                var dnsClient = new DnsClient(fallbackDns);
-                foreach (var address in await this.LookupAsync(dnsClient, domain, cancellationToken))
+                foreach (var address in await this.dnsClient.LookupAsync(dns, domain, cancellationToken))
                 {
                     if (hashSet.Add(address) == true)
                     {
@@ -146,26 +124,20 @@ namespace FastGithub.DomainResolve
         }
 
         /// <summary>
-        /// 查找ip
+        /// 获取dns服务
         /// </summary>
-        /// <param name="dnsClient"></param>
-        /// <param name="domain"></param>
-        /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        private async Task<IPAddress[]> LookupAsync(DnsClient dnsClient, string domain, CancellationToken cancellationToken)
+        private IEnumerable<IPEndPoint> GetDnsServers()
         {
-            try
+            var cryptDns = this.dnscryptProxy.LocalEndPoint;
+            if (cryptDns != null)
             {
-                var addresses = await dnsClient.LookupAsync(domain, cancellationToken);
-                var items = string.Join(", ", addresses.Select(item => item.ToString()));
-                this.logger.LogInformation($"{dnsClient}:{domain}->[{items}]");
-                return addresses;
+                yield return cryptDns;
             }
-            catch (Exception ex)
+
+            foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
             {
-                cancellationToken.ThrowIfCancellationRequested();
-                this.logger.LogWarning($"{dnsClient}无法解析{domain}:{ex.Message}");
-                return Array.Empty<IPAddress>();
+                yield return fallbackDns;
             }
         }
     }

+ 4 - 4
FastGithub.DomainResolve/IDomainResolver.cs

@@ -11,15 +11,15 @@ namespace FastGithub.DomainResolve
     public interface IDomainResolver
     {
         /// <summary>
-        /// 解析域名
+        /// 解析可用的ip
         /// </summary>
-        /// <param name="domain"></param>
+        /// <param name="endPoint">远程节点</param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        Task<IPAddress> ResolveAsync(DnsEndPoint domain, CancellationToken cancellationToken = default);
+        Task<IPAddress> ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default);
 
         /// <summary>
-        /// 解析域名
+        /// 解析所有ip
         /// </summary>
         /// <param name="domain">域名</param>
         /// <param name="cancellationToken"></param>

+ 2 - 1
FastGithub.DomainResolve/ServiceCollectionExtensions.cs

@@ -15,7 +15,8 @@ namespace FastGithub
         /// <param name="services"></param> 
         /// <returns></returns>
         public static IServiceCollection AddDomainResolve(this IServiceCollection services)
-        { 
+        {
+            services.TryAddSingleton<DnsClient>();
             services.TryAddSingleton<DnscryptProxy>();
             services.TryAddSingleton<IDomainResolver, DomainResolver>();
             return services.AddHostedService<DnscryptProxyHostedService>();

+ 0 - 1
FastGithub.PacketIntercept/Dns/DnsInterceptor.cs

@@ -164,7 +164,6 @@ namespace FastGithub.PacketIntercept.Dns
 
             winDivertAddress.Impostor = true;
             WinDivert.WinDivertHelperCalcChecksums(winDivertBuffer, packetLength, ref winDivertAddress, WinDivertChecksumHelperParam.All);
-            this.logger.LogInformation($"{domain} => {IPAddress.Loopback}");
         }