소스 검색

dnscrypt-proxy使用随机端口;
支持多个回退DNS上游;

老九 4 년 전
부모
커밋
bec32d2e35

+ 4 - 12
FastGithub.Configuration/FastGithubConfig.cs

@@ -19,15 +19,9 @@ namespace FastGithub.Configuration
         private ConcurrentDictionary<string, DomainConfig?> domainConfigCache;
 
         /// <summary>
-        /// 未污染的dns
-        /// </summary>  
-        public IPEndPoint PureDns { get; private set; }
-
-        /// <summary>
-        /// 速度快的dns
+        /// 回退的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?>();
             }

+ 4 - 8
FastGithub.Configuration/FastGithubOptions.cs

@@ -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>
         /// 代理的域名配置

+ 39 - 0
FastGithub.Configuration/LocalMachine.cs

@@ -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>

+ 1 - 1
FastGithub.Dns/DnsOverUdpHostedService.cs

@@ -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>

+ 3 - 3
FastGithub.Dns/DnsOverUdpServer.cs

@@ -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}");
             }
         }
 

+ 14 - 3
FastGithub.Dns/RequestResolver.cs

@@ -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}");
         }
     }
 }

+ 5 - 4
FastGithub.DomainResolve/DnscryptProxy.cs

@@ -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>

+ 19 - 30
FastGithub.DomainResolve/DnscryptProxyHostedService.cs

@@ -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,19 +34,14 @@ namespace FastGithub.DomainResolve
         /// <returns></returns>
         public async Task StartAsync(CancellationToken cancellationToken)
         {
-            var pureDns = this.fastGithubConfig.PureDns;
-            if (LocalMachine.ContainsIPAddress(pureDns.Address) == true)
+            try
             {
-                this.dnscryptProxy = new DnscryptProxy(pureDns);
-                try
-                {
-                    await this.dnscryptProxy.StartAsync(cancellationToken);
-                    this.logger.LogInformation($"{this.dnscryptProxy}启动成功");
-                }
-                catch (Exception ex)
-                {
-                    this.logger.LogWarning($"{this.dnscryptProxy}启动失败:{ex.Message}");
-                }
+                await this.dnscryptProxy.StartAsync(cancellationToken);
+                this.logger.LogInformation($"{this.dnscryptProxy}启动成功");
+            }
+            catch (Exception ex)
+            {
+                this.logger.LogWarning($"{this.dnscryptProxy}启动失败:{ex.Message}");
             }
         }
 
@@ -59,19 +52,15 @@ namespace FastGithub.DomainResolve
         /// <returns></returns>
         public Task StopAsync(CancellationToken cancellationToken)
         {
-            if (this.dnscryptProxy != null)
+            try
             {
-                try
-                {
-                    this.dnscryptProxy.Stop();
-                    this.logger.LogInformation($"{this.dnscryptProxy}已停止");
-                }
-                catch (Exception ex)
-                {
-                    this.logger.LogWarning($"{this.dnscryptProxy}停止失败:{ex.Message}");
-                }
+                this.dnscryptProxy.Stop();
+                this.logger.LogInformation($"{this.dnscryptProxy}已停止");
+            }
+            catch (Exception ex)
+            {
+                this.logger.LogWarning($"{this.dnscryptProxy}停止失败:{ex.Message}");
             }
-
             return Task.CompletedTask;
         }
     }

+ 45 - 19
FastGithub.DomainResolve/DomainResolver.cs

@@ -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>

+ 2 - 2
FastGithub.DomainResolve/IDomainResolver.cs

@@ -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);
     }
 }

+ 1 - 0
FastGithub.DomainResolve/ServiceCollectionExtensions.cs

@@ -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>();
         }

+ 10 - 8
FastGithub/appsettings.json

@@ -5,14 +5,16 @@
       "SshPort": 22, // ssh监听的端口,修改后重启应用才生效
       "DnsPort": 53 // dns监听的端口,修改后重启应用才生效
     },
-    "PureDns": { // 用于解析DomainConfigs的域名
-      "IPAddress": "127.0.0.1",
-      "Port": 5533 // 5533指向dnscrypt-proxy
-    },
-    "FastDns": { // 用于解析不在DomainConfigs的域名
-      "IPAddress": "114.114.114.114",
-      "Port": 53
-    },
+    "FallbackDns": [ // 用于解析不在DomainConfigs的域名
+      {
+        "IPAddress": "114.114.114.114",
+        "Port": 53
+      },
+      {
+        "IPAddress": "8.8.8.8",
+        "Port": 53
+      }
+    ],
     "DomainConfigs": {
       "*.x.y.z.com": { // 域名的*表示除.之外0到多个任意字符
         "TlsSni": false, // 指示tls握手时是否发送SNI