using FastGithub.Configuration; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace FastGithub.DomainResolve { /// /// 域名解析器 /// sealed class DomainResolver : IDomainResolver { private readonly DnsClient dnsClient; private readonly FastGithubConfig fastGithubConfig; private readonly ILogger logger; private record EndPointItem(string Host, int Port); private readonly ConcurrentDictionary dnsEndPointAddressElapseds = new(); private static readonly string dnsEndpointFile = "dnsendpoints.json"; private static readonly SemaphoreSlim dnsEndpointLocker = new(1, 1); private static readonly JsonSerializerOptions jsonOptions = new() { WriteIndented = true, PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; /// /// 域名解析器 /// /// /// /// public DomainResolver( DnsClient dnsClient, FastGithubConfig fastGithubConfig, ILogger logger) { this.dnsClient = dnsClient; this.fastGithubConfig = fastGithubConfig; this.logger = logger; foreach (var endPoint in this.ReadDnsEndPoints()) { this.dnsEndPointAddressElapseds.TryAdd(endPoint, IPAddressElapsedCollection.Empty); } } /// /// 读取保存的节点 /// /// private IList ReadDnsEndPoints() { if (File.Exists(dnsEndpointFile) == false) { return Array.Empty(); } try { dnsEndpointLocker.Wait(); var utf8Json = File.ReadAllBytes(dnsEndpointFile); var endPointItems = JsonSerializer.Deserialize(utf8Json, jsonOptions); if (endPointItems == null) { return Array.Empty(); } var dnsEndPoints = new List(); foreach (var item in endPointItems) { if (this.fastGithubConfig.IsMatch(item.Host) == true) { dnsEndPoints.Add(new DnsEndPoint(item.Host, item.Port)); } } return dnsEndPoints; } catch (Exception ex) { this.logger.LogWarning(ex.Message, "读取dns记录异常"); return Array.Empty(); } finally { dnsEndpointLocker.Release(); } } /// /// 保存节点到文件 /// /// /// /// private async Task WriteDnsEndPointsAsync(IEnumerable dnsEndPoints, CancellationToken cancellationToken) { try { await dnsEndpointLocker.WaitAsync(CancellationToken.None); var endPointItems = dnsEndPoints.Select(item => new EndPointItem(item.Host, item.Port)).ToArray(); var utf8Json = JsonSerializer.SerializeToUtf8Bytes(endPointItems, jsonOptions); await File.WriteAllBytesAsync(dnsEndpointFile, utf8Json, cancellationToken); } catch (Exception ex) { this.logger.LogWarning(ex.Message, "保存dns记录异常"); } finally { dnsEndpointLocker.Release(); } } /// /// 解析ip /// /// 节点 /// /// public async Task ResolveAnyAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default) { await foreach (var address in this.ResolveAllAsync(endPoint, cancellationToken)) { return address; } throw new FastGithubException($"解析不到{endPoint.Host}的IP"); } /// /// 解析域名 /// /// 节点 /// /// public async IAsyncEnumerable ResolveAllAsync(DnsEndPoint endPoint, [EnumeratorCancellation] CancellationToken cancellationToken) { if (this.dnsEndPointAddressElapseds.TryGetValue(endPoint, out var addressElapseds) && addressElapseds.IsEmpty == false) { this.logger.LogInformation($"{endPoint.Host}->{addressElapseds}"); foreach (var addressElapsed in addressElapseds) { yield return addressElapsed.Adddress; } } else { if (this.dnsEndPointAddressElapseds.TryAdd(endPoint, IPAddressElapsedCollection.Empty)) { await this.WriteDnsEndPointsAsync(this.dnsEndPointAddressElapseds.Keys, cancellationToken); } await foreach (var adddress in this.dnsClient.ResolveAsync(endPoint, fastSort: true, cancellationToken)) { this.logger.LogInformation($"{endPoint.Host}->{adddress}"); yield return adddress; } } } /// /// 对所有节点进行测速 /// /// /// public async Task TestAllEndPointsAsync(CancellationToken cancellationToken) { foreach (var keyValue in this.dnsEndPointAddressElapseds) { if (keyValue.Value.IsEmpty || keyValue.Value.IsExpired) { var dnsEndPoint = keyValue.Key; var addresses = new List(); await foreach (var adddress in this.dnsClient.ResolveAsync(dnsEndPoint, fastSort: false, cancellationToken)) { addresses.Add(adddress); } var addressElapseds = IPAddressElapsedCollection.Empty; if (addresses.Count == 1) { var addressElapsed = new IPAddressElapsed(addresses[0], TimeSpan.Zero); addressElapseds = new IPAddressElapsedCollection(addressElapsed); } else if (addresses.Count > 1) { var tasks = addresses.Select(address => IPAddressElapsed.ParseAsync(address, dnsEndPoint.Port, cancellationToken)); addressElapseds = new IPAddressElapsedCollection(await Task.WhenAll(tasks)); } this.dnsEndPointAddressElapseds[dnsEndPoint] = addressElapseds; } } } } }