diff --git a/FastGithub.Configuration/FastGithubConfig.cs b/FastGithub.Configuration/FastGithubConfig.cs index 424f593..21ba6c0 100644 --- a/FastGithub.Configuration/FastGithubConfig.cs +++ b/FastGithub.Configuration/FastGithubConfig.cs @@ -19,15 +19,9 @@ namespace FastGithub.Configuration private ConcurrentDictionary domainConfigCache; /// - /// 未污染的dns - /// - public IPEndPoint PureDns { get; private set; } - - /// - /// 速度快的dns + /// 回退的dns /// - public IPEndPoint FastDns { get; private set; } - + public IPEndPoint[] FallbackDns { get; set; } /// /// 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(); @@ -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(); } diff --git a/FastGithub.Configuration/FastGithubOptions.cs b/FastGithub.Configuration/FastGithubOptions.cs index e50884a..013414a 100644 --- a/FastGithub.Configuration/FastGithubOptions.cs +++ b/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(); /// - /// 未污染的dns + /// 回退的dns /// - public DnsConfig PureDns { get; set; } = new DnsConfig { IPAddress = "127.0.0.1", Port = 5533 }; - - /// - /// 速度快的dns - /// - public DnsConfig FastDns { get; set; } = new DnsConfig { IPAddress = "114.114.114.114", Port = 53 }; + public DnsConfig[] FallbackDns { get; set; } = Array.Empty(); /// /// 代理的域名配置 diff --git a/FastGithub.Configuration/LocalMachine.cs b/FastGithub.Configuration/LocalMachine.cs index a9ed7e1..f8db286 100644 --- a/FastGithub.Configuration/LocalMachine.cs +++ b/FastGithub.Configuration/LocalMachine.cs @@ -79,6 +79,45 @@ namespace FastGithub.Configuration } } + /// + /// 获取可用的随机端口 + /// + /// + /// 最小值 + /// + public static int GetAvailablePort(AddressFamily addressFamily, int min = 1024) + { + var hashSet = new HashSet(); + 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("当前无可用的端口"); + } + /// /// 是否可以监听指定tcp端口 /// diff --git a/FastGithub.Dns/DnsOverUdpHostedService.cs b/FastGithub.Dns/DnsOverUdpHostedService.cs index b8b147c..2a1c8c0 100644 --- a/FastGithub.Dns/DnsOverUdpHostedService.cs +++ b/FastGithub.Dns/DnsOverUdpHostedService.cs @@ -100,7 +100,7 @@ namespace FastGithub.Dns /// protected override Task ExecuteAsync(CancellationToken stoppingToken) { - return this.dnsOverUdpServer.ListenAsync(stoppingToken); + return this.dnsOverUdpServer.HandleAsync(stoppingToken); } /// diff --git a/FastGithub.Dns/DnsOverUdpServer.cs b/FastGithub.Dns/DnsOverUdpServer.cs index 0b5b642..8ad3d94 100644 --- a/FastGithub.Dns/DnsOverUdpServer.cs +++ b/FastGithub.Dns/DnsOverUdpServer.cs @@ -60,11 +60,11 @@ namespace FastGithub.Dns } /// - /// 监听dns请求 + /// 监听和处理dns请求 /// /// /// - 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}"); } } diff --git a/FastGithub.Dns/RequestResolver.cs b/FastGithub.Dns/RequestResolver.cs index e1ee910..04ae858 100644 --- a/FastGithub.Dns/RequestResolver.cs +++ b/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}"); } } } diff --git a/FastGithub.DomainResolve/DnscryptProxy.cs b/FastGithub.DomainResolve/DnscryptProxy.cs index c073c04..a8f47c7 100644 --- a/FastGithub.DomainResolve/DnscryptProxy.cs +++ b/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 /// /// DnscryptProxy服务 /// - /// 监听的节点 - 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); } /// diff --git a/FastGithub.DomainResolve/DnscryptProxyHostedService.cs b/FastGithub.DomainResolve/DnscryptProxyHostedService.cs index 135164b..635f7b6 100644 --- a/FastGithub.DomainResolve/DnscryptProxyHostedService.cs +++ b/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 /// sealed class DnscryptProxyHostedService : IHostedService { - private readonly FastGithubConfig fastGithubConfig; private readonly ILogger logger; - private DnscryptProxy? dnscryptProxy; + private readonly DnscryptProxy dnscryptProxy; /// /// DnscryptProxy后台服务 /// - /// + /// /// public DnscryptProxyHostedService( - FastGithubConfig fastGithubConfig, + DnscryptProxy dnscryptProxy, ILogger logger) { - this.fastGithubConfig = fastGithubConfig; + this.dnscryptProxy = dnscryptProxy; this.logger = logger; } @@ -36,19 +34,14 @@ namespace FastGithub.DomainResolve /// 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 /// 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; } } diff --git a/FastGithub.DomainResolve/DomainResolver.cs b/FastGithub.DomainResolve/DomainResolver.cs index dd76c56..60fdc06 100644 --- a/FastGithub.DomainResolve/DomainResolver.cs +++ b/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 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 semaphoreSlims = new(); /// @@ -35,14 +36,17 @@ namespace FastGithub.DomainResolve /// /// /// + /// /// public DomainResolver( IMemoryCache memoryCache, FastGithubConfig fastGithubConfig, + DnscryptProxy dnscryptProxy, ILogger logger) { this.memoryCache = memoryCache; this.fastGithubConfig = fastGithubConfig; + this.dnscryptProxy = dnscryptProxy; this.logger = logger; } @@ -69,65 +73,86 @@ namespace FastGithub.DomainResolve /// /// 查找ip /// - /// + /// /// /// - private async Task LookupAsync(DnsEndPoint endPoint, CancellationToken cancellationToken) + private async Task LookupAsync(DnsEndPoint target, CancellationToken cancellationToken) { - if (this.memoryCache.TryGetValue(endPoint, out var address)) + if (this.memoryCache.TryGetValue(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; } + /// + /// 回退查找ip + /// + /// + /// + /// + private async Task 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; + } + + /// /// 查找ip /// /// - /// + /// /// /// - private async Task LookupCoreAsync(IPEndPoint dns, DnsEndPoint endPoint, CancellationToken cancellationToken) + private async Task 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; } } + /// /// 获取最快的ip /// @@ -152,6 +177,7 @@ namespace FastGithub.DomainResolve return await fastTask; } + /// /// 验证远程节点是否可连接 /// diff --git a/FastGithub.DomainResolve/IDomainResolver.cs b/FastGithub.DomainResolve/IDomainResolver.cs index 2d2674f..5e5a257 100644 --- a/FastGithub.DomainResolve/IDomainResolver.cs +++ b/FastGithub.DomainResolve/IDomainResolver.cs @@ -12,9 +12,9 @@ namespace FastGithub.DomainResolve /// /// 解析域名 /// - /// + /// /// /// - Task ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default); + Task ResolveAsync(DnsEndPoint target, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/FastGithub.DomainResolve/ServiceCollectionExtensions.cs b/FastGithub.DomainResolve/ServiceCollectionExtensions.cs index 1244eb4..8e1cd7f 100644 --- a/FastGithub.DomainResolve/ServiceCollectionExtensions.cs +++ b/FastGithub.DomainResolve/ServiceCollectionExtensions.cs @@ -17,6 +17,7 @@ namespace FastGithub public static IServiceCollection AddDomainResolve(this IServiceCollection services) { services.AddMemoryCache(); + services.TryAddSingleton(); services.TryAddSingleton(); return services.AddHostedService(); } diff --git a/FastGithub/appsettings.json b/FastGithub/appsettings.json index 44858aa..8f8515d 100644 --- a/FastGithub/appsettings.json +++ b/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