using DNS.Client; using DNS.Protocol; 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.Threading; using System.Threading.Tasks; namespace FastGithub.DomainResolve { /// /// 域名解析器 /// sealed class DomainResolver : IDomainResolver { private readonly IMemoryCache domainResolveCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly IMemoryCache disableIPAddressCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly FastGithubConfig fastGithubConfig; private readonly DnscryptProxy dnscryptProxy; private readonly ILogger logger; private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(5d); private readonly TimeSpan disableIPExpiration = TimeSpan.FromMinutes(2d); private readonly TimeSpan dnscryptExpiration = TimeSpan.FromMinutes(10d); private readonly TimeSpan systemExpiration = TimeSpan.FromMinutes(2d); private readonly TimeSpan loopbackExpiration = TimeSpan.FromSeconds(5d); private readonly ConcurrentDictionary semaphoreSlims = new(); /// /// 域名解析器 /// /// /// /// public DomainResolver( FastGithubConfig fastGithubConfig, DnscryptProxy dnscryptProxy, ILogger logger) { this.fastGithubConfig = fastGithubConfig; this.dnscryptProxy = dnscryptProxy; this.logger = logger; } /// /// 设置ip不可用 /// /// ip public void SetDisabled(IPAddress address) { this.disableIPAddressCache.Set(address, address, this.disableIPExpiration); } /// /// 刷新域名解析结果 /// /// 域名 public void FlushDomain(DnsEndPoint domain) { this.domainResolveCache.Remove(domain); } /// /// 解析域名 /// /// /// /// /// /// public async Task ResolveAsync(DnsEndPoint domain, CancellationToken cancellationToken = default) { var semaphore = this.semaphoreSlims.GetOrAdd(domain, _ => new SemaphoreSlim(1, 1)); try { await semaphore.WaitAsync(); return await this.ResolveCoreAsync(domain, cancellationToken); } finally { semaphore.Release(); } } /// /// 解析域名 /// /// /// /// /// /// private async Task ResolveCoreAsync(DnsEndPoint domain, CancellationToken cancellationToken) { if (this.domainResolveCache.TryGetValue(domain, out var address) && address != null) { return address; } var expiration = this.dnscryptExpiration; address = await this.LookupByDnscryptAsync(domain, cancellationToken); if (address == null) { address = await this.LookupByDnscryptAsync(domain, cancellationToken); } if (address == null && OperatingSystem.IsWindows() == false) { expiration = this.systemExpiration; address = await this.LookupByDnsSystemAsync(domain, cancellationToken); } if (address == null) { throw new FastGithubException($"当前解析不到{domain.Host}可用的ip,请刷新重试"); } // 往往是被污染的dns if (address.Equals(IPAddress.Loopback) == true) { expiration = this.loopbackExpiration; } this.domainResolveCache.Set(domain, address, expiration); return address; } /// /// Dnscrypt查找ip /// /// /// /// private async Task LookupByDnscryptAsync(DnsEndPoint domain, CancellationToken cancellationToken) { var dns = this.dnscryptProxy.LocalEndPoint; if (dns == null) { return null; } try { var dnsClient = new DnsClient(dns); var addresses = await dnsClient.Lookup(domain.Host, RecordType.A, cancellationToken); var address = await this.FindFastValueAsync(addresses, domain.Port, cancellationToken); if (address == null) { this.logger.LogWarning($"dns({dns})解析不到{domain.Host}可用的ip解析"); } else { this.logger.LogInformation($"dns({dns}): {domain.Host}->{address}"); } return address; } catch (Exception ex) { cancellationToken.ThrowIfCancellationRequested(); this.logger.LogWarning($"dns({dns})无法解析{domain.Host}:{ex.Message}"); return null; } } /// /// 系统DNS查找ip /// /// /// /// /// private async Task LookupByDnsSystemAsync(DnsEndPoint domain, CancellationToken cancellationToken) { try { var allAddresses = await Dns.GetHostAddressesAsync(domain.Host); var addresses = allAddresses.Where(item => item.AddressFamily == AddressFamily.InterNetwork); var address = await this.FindFastValueAsync(addresses, domain.Port, cancellationToken); if (address == null) { this.logger.LogWarning($"dns(系统)解析不到{domain.Host}可用的ip解析"); } else { this.logger.LogInformation($"dns(系统): {domain.Host}->{address}"); } return address; } catch (Exception ex) { cancellationToken.ThrowIfCancellationRequested(); this.logger.LogWarning($"dns(系统)无法解析{domain.Host}:{ex.Message}"); return default; } } /// /// 获取最快的ip /// /// /// /// /// /// private async Task FindFastValueAsync(IEnumerable addresses, int port, CancellationToken cancellationToken) { addresses = addresses.Where(IsEnableIPAddress).ToArray(); 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; bool IsEnableIPAddress(IPAddress address) { return this.disableIPAddressCache.TryGetValue(address, out _) == false; } } /// /// 验证远程节点是否可连接 /// /// /// /// /// /// 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) { cancellationToken.ThrowIfCancellationRequested(); this.SetDisabled(address); return default; } catch (Exception) { this.SetDisabled(address); await Task.Delay(this.connectTimeout, cancellationToken); return default; } } } }