using FastGithub.Configuration; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace FastGithub.DomainResolve { /// /// 域名解析器 /// sealed class DomainResolver : IDomainResolver { private readonly DnscryptProxy dnscryptProxy; private readonly FastGithubConfig fastGithubConfig; private readonly DnsClient dnsClient; private readonly ConcurrentDictionary semaphoreSlims = new(); private readonly IMemoryCache ipEndPointAvailableCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly TimeSpan ipEndPointExpiration = TimeSpan.FromMinutes(2d); private readonly TimeSpan ipEndPointConnectTimeout = TimeSpan.FromSeconds(5d); /// /// 域名解析器 /// /// /// /// public DomainResolver( DnscryptProxy dnscryptProxy, FastGithubConfig fastGithubConfig, DnsClient dnsClient) { this.dnscryptProxy = dnscryptProxy; this.fastGithubConfig = fastGithubConfig; this.dnsClient = dnsClient; } /// /// 解析可用的ip /// /// 远程节点 /// /// public async Task ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default) { await foreach (var address in this.ResolveAsync(endPoint.Host, cancellationToken)) { if (await this.IsAvailableAsync(new IPEndPoint(address, endPoint.Port), cancellationToken)) { return address; } } throw new FastGithubException($"解析不到{endPoint.Host}可用的IP"); } /// /// 验证远程节点是否可连接 /// /// /// /// /// private async Task IsAvailableAsync(IPEndPoint ipEndPoint, CancellationToken cancellationToken) { var semaphore = this.semaphoreSlims.GetOrAdd(ipEndPoint, _ => new SemaphoreSlim(1, 1)); try { await semaphore.WaitAsync(CancellationToken.None); if (this.ipEndPointAvailableCache.TryGetValue(ipEndPoint, out var available)) { return available; } try { using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); using var timeoutTokenSource = new CancellationTokenSource(this.ipEndPointConnectTimeout); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token); await socket.ConnectAsync(ipEndPoint, linkedTokenSource.Token); available = true; } catch (Exception) { cancellationToken.ThrowIfCancellationRequested(); available = false; } this.ipEndPointAvailableCache.Set(ipEndPoint, available, ipEndPointExpiration); return available; } finally { semaphore.Release(); } } /// /// 解析域名 /// /// 域名 /// /// public async IAsyncEnumerable ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken) { var hashSet = new HashSet(); foreach (var dns in this.GetDnsServers()) { foreach (var address in await this.dnsClient.LookupAsync(dns, domain, cancellationToken)) { if (hashSet.Add(address) == true) { yield return address; } } } } /// /// 获取dns服务 /// /// private IEnumerable GetDnsServers() { var cryptDns = this.dnscryptProxy.LocalEndPoint; if (cryptDns != null) { yield return cryptDns; } foreach (var fallbackDns in this.fastGithubConfig.FallbackDns) { yield return fallbackDns; } } } }