using DNS.Client; using DNS.Protocol; using FastGithub.Configuration; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace FastGithub.DomainResolve { /// /// 域名解析器 /// sealed class DomainResolver : IDomainResolver { private readonly IMemoryCache memoryCache; private readonly FastGithubConfig fastGithubConfig; private readonly ILogger logger; private readonly TimeSpan lookupTimeout = TimeSpan.FromSeconds(1d); private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(2d); private readonly TimeSpan resolveCacheTimeSpan = TimeSpan.FromMinutes(2d); private readonly ConcurrentDictionary semaphoreSlims = new(); /// /// 域名解析器 /// /// /// /// public DomainResolver( IMemoryCache memoryCache, FastGithubConfig fastGithubConfig, ILogger logger) { this.memoryCache = memoryCache; this.fastGithubConfig = fastGithubConfig; this.logger = logger; } /// /// 解析域名 /// /// /// /// public async Task ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default) { var semaphore = this.semaphoreSlims.GetOrAdd(endPoint, _ => new SemaphoreSlim(1, 1)); try { await semaphore.WaitAsync(cancellationToken); if (this.memoryCache.TryGetValue(endPoint, out var address) == false) { address = await this.LookupAsync(endPoint, cancellationToken); this.memoryCache.Set(endPoint, address, this.resolveCacheTimeSpan); } return address; } finally { semaphore.Release(); } } /// /// 查找ip /// /// /// /// private async Task LookupAsync(DnsEndPoint endPoint, CancellationToken cancellationToken) { var dnsEndPoints = new[] { this.fastGithubConfig.PureDns, this.fastGithubConfig.FastDns }; foreach (var dns in dnsEndPoints) { var addresses = await this.LookupCoreAsync(dns, endPoint, cancellationToken); var fastAddress = await this.GetFastIPAddressAsync(addresses, endPoint.Port, cancellationToken); if (fastAddress != null) { this.logger.LogInformation($"[{endPoint.Host}->{fastAddress}]"); return fastAddress; } } throw new FastGithubException($"解析不到{endPoint.Host}可用的ip"); } /// /// 查找ip /// /// /// /// /// private async Task> LookupCoreAsync(IPEndPoint dns, DnsEndPoint endPoint, CancellationToken cancellationToken) { try { var dnsClient = new DnsClient(dns); using var timeoutTokenSource = new CancellationTokenSource(this.lookupTimeout); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token); return await dnsClient.Lookup(endPoint.Host, RecordType.A, linkedTokenSource.Token); } catch { this.logger.LogWarning($"dns({dns})无法解析{endPoint.Host}"); return Enumerable.Empty(); } } /// /// 获取最快的ip /// /// /// /// /// private async Task GetFastIPAddressAsync(IEnumerable addresses, int port, CancellationToken cancellationToken) { if (addresses.Any() == false) { return default; } if (port <= 0) { return addresses.FirstOrDefault(); } var tasks = addresses.Select(address => this.IsAvailableAsync(address, port, cancellationToken)); var fastTask = await Task.WhenAny(tasks); return await fastTask; } /// /// 验证远程节点是否可连接 /// /// /// /// /// private async Task IsAvailableAsync(IPAddress address, int port, CancellationToken cancellationToken) { try { using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token); await socket.ConnectAsync(address, port, linkedTokenSource.Token); return address; } catch (OperationCanceledException) { return default; } catch (Exception) { await Task.Delay(this.connectTimeout, cancellationToken); return default; } } } }