diff --git a/FastGithub.DomainResolve/DnsClient.cs b/FastGithub.DomainResolve/DnsClient.cs index fb02214..29e4996 100644 --- a/FastGithub.DomainResolve/DnsClient.cs +++ b/FastGithub.DomainResolve/DnsClient.cs @@ -11,6 +11,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.NetworkInformation; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -31,9 +32,11 @@ namespace FastGithub.DomainResolve private readonly ConcurrentDictionary semaphoreSlims = new(); private readonly IMemoryCache dnsCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); - private readonly TimeSpan dnsExpiration = TimeSpan.FromMinutes(1d); + private readonly TimeSpan defaultEmptyTtl = TimeSpan.FromSeconds(30d); private readonly int resolveTimeout = (int)TimeSpan.FromSeconds(2d).TotalMilliseconds; + private record LookupResult(IPAddress[] Addresses, TimeSpan TimeToLive); + /// /// DNS客户端 /// @@ -56,21 +59,12 @@ namespace FastGithub.DomainResolve /// 域名 /// /// - public async IAsyncEnumerable ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken) { var hashSet = new HashSet(); foreach (var dns in this.GetDnsServers()) { var addresses = await this.LookupAsync(dns, domain, cancellationToken); - var value = Filter(hashSet, addresses).ToArray(); - if (value.Length > 0) - { - yield return value; - } - } - - static IEnumerable Filter(HashSet hashSet, IPAddress[] addresses) - { foreach (var address in addresses) { if (hashSet.Add(address) == true) @@ -116,15 +110,18 @@ namespace FastGithub.DomainResolve try { - if (this.dnsCache.TryGetValue(key, out var value) == false) + if (this.dnsCache.TryGetValue(key, out var value)) { - value = await this.LookupCoreAsync(dns, domain, cancellationToken); - this.dnsCache.Set(key, value, this.dnsExpiration); - - var items = string.Join(", ", value.Select(item => item.ToString())); - this.logger.LogInformation($"dns://{dns}:{domain}->[{items}]"); + return value; } - return value; + + var result = await this.LookupCoreAsync(dns, domain, cancellationToken); + this.dnsCache.Set(key, result.Addresses, result.TimeToLive); + + var items = string.Join(", ", result.Addresses.Select(item => item.ToString())); + this.logger.LogInformation($"dns://{dns}:{domain}->[{items}]"); + + return result.Addresses; } catch (Exception ex) { @@ -144,11 +141,11 @@ namespace FastGithub.DomainResolve /// /// /// - private async Task LookupCoreAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default) + private async Task LookupCoreAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default) { if (domain == LOCALHOST) { - return new[] { IPAddress.Loopback }; + return new LookupResult(new[] { IPAddress.Loopback }, TimeSpan.MaxValue); } var resolver = dns.Port == DNS_PORT @@ -165,11 +162,70 @@ namespace FastGithub.DomainResolve var clientRequest = new ClientRequest(resolver, request); var response = await clientRequest.Resolve(cancellationToken); - return response.AnswerRecords + var addresses = response.AnswerRecords .OfType() .Where(item => IPAddress.IsLoopback(item.IPAddress) == false) .Select(item => item.IPAddress) .ToArray(); + + if (addresses.Length == 0) + { + return new LookupResult(addresses, this.defaultEmptyTtl); + } + + if (addresses.Length > 1) + { + addresses = await OrderByPingAnyAsync(addresses); + } + + var timeToLive = response.AnswerRecords.First().TimeToLive; + if (timeToLive <= TimeSpan.Zero) + { + timeToLive = this.defaultEmptyTtl; + } + + this.logger.LogWarning($"{domain} [{timeToLive}]"); + return new LookupResult(addresses, timeToLive); + } + + /// + /// ping排序 + /// + /// + /// + private static async Task OrderByPingAnyAsync(IPAddress[] addresses) + { + var fastedAddress = await await Task.WhenAny(addresses.Select(address => PingAsync(address))); + if (fastedAddress == null) + { + return addresses; + } + + var hashSet = new HashSet { fastedAddress }; + foreach (var address in addresses) + { + hashSet.Add(address); + } + return hashSet.ToArray(); + } + + /// + /// ping请求 + /// + /// + /// + private static async Task PingAsync(IPAddress address) + { + try + { + using var ping = new Ping(); + var reply = await ping.SendPingAsync(address); + return reply.Status == IPStatus.Success ? address : default; + } + catch (Exception) + { + return default; + } } } } diff --git a/FastGithub.DomainResolve/DomainResolveHostedService.cs b/FastGithub.DomainResolve/DnscryptProxyHostedService.cs similarity index 55% rename from FastGithub.DomainResolve/DomainResolveHostedService.cs rename to FastGithub.DomainResolve/DnscryptProxyHostedService.cs index ba2a4df..10ae281 100644 --- a/FastGithub.DomainResolve/DomainResolveHostedService.cs +++ b/FastGithub.DomainResolve/DnscryptProxyHostedService.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Hosting; -using System; using System.Threading; using System.Threading.Tasks; @@ -8,25 +7,17 @@ namespace FastGithub.DomainResolve /// /// 域名解析后台服务 /// - sealed class DomainResolveHostedService : BackgroundService + sealed class DnscryptProxyHostedService : BackgroundService { private readonly DnscryptProxy dnscryptProxy; - private readonly DomainSpeedTester speedTester; - - private readonly TimeSpan speedTestDueTime = TimeSpan.FromSeconds(10d); - private readonly TimeSpan speedTestPeriod = TimeSpan.FromMinutes(2d); /// /// 域名解析后台服务 /// - /// - /// - public DomainResolveHostedService( - DnscryptProxy dnscryptProxy, - DomainSpeedTester speedTester) + /// + public DnscryptProxyHostedService(DnscryptProxy dnscryptProxy) { this.dnscryptProxy = dnscryptProxy; - this.speedTester = speedTester; } /// @@ -37,13 +28,6 @@ namespace FastGithub.DomainResolve protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await this.dnscryptProxy.StartAsync(stoppingToken); - await Task.Delay(this.speedTestDueTime, stoppingToken); - - while (stoppingToken.IsCancellationRequested == false) - { - await this.speedTester.TestSpeedAsync(stoppingToken); - await Task.Delay(this.speedTestPeriod, stoppingToken); - } } /// diff --git a/FastGithub.DomainResolve/DomainResolver.cs b/FastGithub.DomainResolve/DomainResolver.cs index 4d15ea1..67e24c5 100644 --- a/FastGithub.DomainResolve/DomainResolver.cs +++ b/FastGithub.DomainResolve/DomainResolver.cs @@ -1,7 +1,6 @@ using FastGithub.Configuration; using System.Collections.Generic; using System.Net; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -12,15 +11,15 @@ namespace FastGithub.DomainResolve /// sealed class DomainResolver : IDomainResolver { - private readonly DomainSpeedTester speedTester; + private readonly DnsClient dnsClient; /// /// 域名解析器 /// - /// - public DomainResolver(DomainSpeedTester speedTester) + /// + public DomainResolver(DnsClient dnsClient) { - this.speedTester = speedTester; + this.dnsClient = dnsClient; } /// @@ -29,7 +28,7 @@ namespace FastGithub.DomainResolve /// 域名 /// /// - public async Task ResolveAsync(string domain, CancellationToken cancellationToken = default) + public async Task ResolveAnyAsync(string domain, CancellationToken cancellationToken = default) { await foreach (var address in this.ResolveAllAsync(domain, cancellationToken)) { @@ -44,23 +43,9 @@ namespace FastGithub.DomainResolve /// 域名 /// /// - public async IAsyncEnumerable ResolveAllAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken) + public IAsyncEnumerable ResolveAllAsync(string domain, CancellationToken cancellationToken) { - if (this.speedTester.TryGetOrderAllIPAddresses(domain, out var addresses)) - { - foreach (var address in addresses) - { - yield return address; - } - } - else - { - this.speedTester.Add(domain); - await foreach (var address in this.speedTester.GetOrderAnyIPAddressAsync(domain, cancellationToken)) - { - yield return address; - } - } + return this.dnsClient.ResolveAsync(domain, cancellationToken); } } } diff --git a/FastGithub.DomainResolve/DomainSpeedTester.cs b/FastGithub.DomainResolve/DomainSpeedTester.cs deleted file mode 100644 index ddd7682..0000000 --- a/FastGithub.DomainResolve/DomainSpeedTester.cs +++ /dev/null @@ -1,178 +0,0 @@ -using FastGithub.Configuration; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -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 -{ - /// - /// 域名的IP测速服务 - /// - sealed class DomainSpeedTester - { - private const string DOMAINS_JSON_FILE = "domains.json"; - - private readonly DnsClient dnsClient; - private readonly ILogger logger; - - private readonly object syncRoot = new(); - private readonly Dictionary domainIPAddressHashSet = new(); - - /// - /// 域名的IP测速服务 - /// - /// - /// - public DomainSpeedTester( - DnsClient dnsClient, - ILogger logger) - { - this.dnsClient = dnsClient; - this.logger = logger; - - try - { - this.LoadDomains(); - } - catch (Exception ex) - { - logger.LogWarning($"加载域名数据失败:{ex.Message}"); - } - } - - /// - /// 加载域名数据 - /// - private void LoadDomains() - { - if (File.Exists(DOMAINS_JSON_FILE) == false) - { - return; - } - - var utf8Json = File.ReadAllBytes(DOMAINS_JSON_FILE); - var domains = JsonSerializer.Deserialize(utf8Json); - if (domains == null) - { - return; - } - - foreach (var domain in domains) - { - this.domainIPAddressHashSet.TryAdd(domain, new IPAddressItemHashSet()); - } - } - - /// - /// 添加要测速的域名 - /// - /// - public void Add(string domain) - { - lock (this.syncRoot) - { - if (this.domainIPAddressHashSet.TryAdd(domain, new IPAddressItemHashSet())) - { - try - { - this.SaveDomains(); - } - catch (Exception ex) - { - logger.LogWarning($"保存域名数据失败:{ex.Message}"); - } - } - } - } - - /// - /// 保存域名 - /// - private void SaveDomains() - { - var domains = this.domainIPAddressHashSet.Keys - .Select(item => new DomainPattern(item)) - .OrderBy(item => item) - .Select(item => item.ToString()) - .ToArray(); - - var utf8Json = JsonSerializer.SerializeToUtf8Bytes(domains, new JsonSerializerOptions { WriteIndented = true }); - File.WriteAllBytes(DOMAINS_JSON_FILE, utf8Json); - } - - - /// - /// 尝试获取测试后排序的IP地址 - /// - /// - /// - /// - public bool TryGetOrderAllIPAddresses(string domain, [MaybeNullWhen(false)] out IPAddress[] addresses) - { - lock (this.syncRoot) - { - if (this.domainIPAddressHashSet.TryGetValue(domain, out var hashSet) && hashSet.Count > 0) - { - addresses = hashSet.ToArray().OrderBy(item => item.PingElapsed).Select(item => item.Address).ToArray(); - return true; - } - } - - addresses = default; - return false; - } - - /// - /// 获取只排序头个元素的IP地址 - /// - /// 域名 - /// - /// - public async IAsyncEnumerable GetOrderAnyIPAddressAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (var addresses in this.dnsClient.ResolveAsync(domain, cancellationToken)) - { - foreach (var address in addresses) - { - yield return address; - } - } - } - - /// - /// 进行一轮IP测速 - /// - /// - /// - public async Task TestSpeedAsync(CancellationToken cancellationToken) - { - KeyValuePair[] keyValues; - lock (this.syncRoot) - { - keyValues = this.domainIPAddressHashSet.ToArray(); - } - - foreach (var keyValue in keyValues) - { - var domain = keyValue.Key; - var hashSet = keyValue.Value; - await foreach (var addresses in this.dnsClient.ResolveAsync(domain, cancellationToken)) - { - foreach (var address in addresses) - { - hashSet.Add(new IPAddressItem(address)); - } - } - await hashSet.PingAllAsync(); - } - } - } -} diff --git a/FastGithub.DomainResolve/FastGithub.DomainResolve.csproj b/FastGithub.DomainResolve/FastGithub.DomainResolve.csproj index ba4e9a8..0ad99be 100644 --- a/FastGithub.DomainResolve/FastGithub.DomainResolve.csproj +++ b/FastGithub.DomainResolve/FastGithub.DomainResolve.csproj @@ -17,16 +17,4 @@ - - - - - - - PreserveNewest - true - PreserveNewest - - - diff --git a/FastGithub.DomainResolve/IDomainResolver.cs b/FastGithub.DomainResolve/IDomainResolver.cs index 384e6ee..e651d0f 100644 --- a/FastGithub.DomainResolve/IDomainResolver.cs +++ b/FastGithub.DomainResolve/IDomainResolver.cs @@ -16,7 +16,7 @@ namespace FastGithub.DomainResolve /// 域名 /// /// - Task ResolveAsync(string domain, CancellationToken cancellationToken = default); + Task ResolveAnyAsync(string domain, CancellationToken cancellationToken = default); /// /// 解析所有ip diff --git a/FastGithub.DomainResolve/IPAddressItem.cs b/FastGithub.DomainResolve/IPAddressItem.cs deleted file mode 100644 index 0e058ea..0000000 --- a/FastGithub.DomainResolve/IPAddressItem.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Net.NetworkInformation; -using System.Threading.Tasks; - -namespace FastGithub.DomainResolve -{ - /// - /// IP地址项 - /// - [DebuggerDisplay("Address = {Address}, PingElapsed = {PingElapsed}")] - sealed class IPAddressItem : IEquatable - { - private readonly Ping ping = new(); - - /// - /// 地址 - /// - public IPAddress Address { get; } - - /// - /// Ping耗时 - /// - public TimeSpan PingElapsed { get; private set; } = TimeSpan.MaxValue; - - /// - /// IP地址项 - /// - /// - public IPAddressItem(IPAddress address) - { - this.Address = address; - } - - /// - /// 发起ping请求 - /// - /// - public async Task PingAsync() - { - try - { - var reply = await this.ping.SendPingAsync(this.Address); - this.PingElapsed = reply.Status == IPStatus.Success - ? TimeSpan.FromMilliseconds(reply.RoundtripTime) - : TimeSpan.MaxValue; - } - catch (Exception) - { - this.PingElapsed = TimeSpan.MaxValue; - } - } - - public bool Equals(IPAddressItem? other) - { - return other != null && other.Address.Equals(this.Address); - } - - public override bool Equals(object? obj) - { - return obj is IPAddressItem other && this.Equals(other); - } - - public override int GetHashCode() - { - return this.Address.GetHashCode(); - } - } -} diff --git a/FastGithub.DomainResolve/IPAddressItemHashSet.cs b/FastGithub.DomainResolve/IPAddressItemHashSet.cs deleted file mode 100644 index d8de684..0000000 --- a/FastGithub.DomainResolve/IPAddressItemHashSet.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace FastGithub.DomainResolve -{ - /// - /// IPAddressItem集合 - /// - sealed class IPAddressItemHashSet - { - private readonly object syncRoot = new(); - private readonly HashSet hashSet = new(); - - /// - /// 获取元素数量 - /// - public int Count => this.hashSet.Count; - - /// - /// 添加元素 - /// - /// - /// - public bool Add(IPAddressItem item) - { - lock (this.syncRoot) - { - return this.hashSet.Add(item); - } - } - - /// - /// 转换为数组 - /// - /// - public IPAddressItem[] ToArray() - { - lock (this.syncRoot) - { - return this.hashSet.ToArray(); - } - } - - /// - /// Ping所有IP - /// - /// - public Task PingAllAsync() - { - var items = this.ToArray(); - if (items.Length == 0) - { - return Task.CompletedTask; - } - if (items.Length == 1) - { - return items[0].PingAsync(); - } - var tasks = items.Select(item => item.PingAsync()); - return Task.WhenAll(tasks); - } - } -} diff --git a/FastGithub.DomainResolve/ServiceCollectionExtensions.cs b/FastGithub.DomainResolve/ServiceCollectionExtensions.cs index 35234a8..68e1583 100644 --- a/FastGithub.DomainResolve/ServiceCollectionExtensions.cs +++ b/FastGithub.DomainResolve/ServiceCollectionExtensions.cs @@ -17,10 +17,9 @@ namespace FastGithub public static IServiceCollection AddDomainResolve(this IServiceCollection services) { services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); - services.AddHostedService(); + services.AddHostedService(); return services; } } diff --git a/FastGithub.DomainResolve/domains.json b/FastGithub.DomainResolve/domains.json deleted file mode 100644 index 71d5fc7..0000000 --- a/FastGithub.DomainResolve/domains.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - "github.com", - "v2ex.com", - "alive.github.com", - "api.github.com", - "collector.githubapp.com", - "github.githubassets.com", - "avatars.githubusercontent.com", - "camo.githubusercontent.com", - "github-releases.githubusercontent.com", - "raw.githubusercontent.com", - "www.gravatar.com", - "onedrive.live.com", - "cdn.v2ex.com", - "microsoft.github.io", - "fonts.geekzu.org", - "gapis.geekzu.org", - "i.stack.imgur.com", - "skyapi.onedrive.live.com", - "codeproject.freetls.fastly.net", - "codeproject.global.ssl.fastly.net" -] \ No newline at end of file diff --git a/FastGithub.Http/HttpClientHandler.cs b/FastGithub.Http/HttpClientHandler.cs index 7dd7e15..97ef818 100644 --- a/FastGithub.Http/HttpClientHandler.cs +++ b/FastGithub.Http/HttpClientHandler.cs @@ -43,7 +43,7 @@ namespace FastGithub.Http /// /// /// - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var uri = request.RequestUri; if (uri == null) @@ -56,18 +56,24 @@ namespace FastGithub.Http var tlsSniValue = this.domainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom(); request.SetRequestContext(new RequestContext(isHttps, tlsSniValue)); - // 设置请求host,修改协议为http + // 设置请求头host,修改协议为http,使用ip取代域名 + var address = await this.domainResolver.ResolveAnyAsync(uri.Host, cancellationToken); + var uriBuilder = new UriBuilder(uri) + { + Scheme = Uri.UriSchemeHttp, + Host = address.ToString() + }; + request.Headers.Host = uri.Host; - request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri; + request.RequestUri = uriBuilder.Uri; if (this.domainConfig.Timeout != null) { using var timeoutTokenSource = new CancellationTokenSource(this.domainConfig.Timeout.Value); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token); - return base.SendAsync(request, linkedTokenSource.Token); + return await base.SendAsync(request, linkedTokenSource.Token); } - - return base.SendAsync(request, cancellationToken); + return await base.SendAsync(request, cancellationToken); } /// @@ -96,7 +102,8 @@ namespace FastGithub.Http private async ValueTask ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken) { var innerExceptions = new List(); - var ipEndPoints = this.GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken); + var dnsEndPoint = new DnsEndPoint(context.InitialRequestMessage.Headers.Host!, context.DnsEndPoint.Port); + var ipEndPoints = this.GetIPEndPointsAsync(dnsEndPoint, cancellationToken); await foreach (var ipEndPoint in ipEndPoints) { @@ -159,7 +166,7 @@ namespace FastGithub.Http return true; } - var domain = context.DnsEndPoint.Host; + var domain = context.InitialRequestMessage.Headers.Host!; var dnsNames = ReadDnsNames(cert); return dnsNames.Any(dns => IsMatch(dns, domain)); } diff --git a/FastGithub.HttpServer/HttpProxyMiddleware.cs b/FastGithub.HttpServer/HttpProxyMiddleware.cs index 194b588..722ed9d 100644 --- a/FastGithub.HttpServer/HttpProxyMiddleware.cs +++ b/FastGithub.HttpServer/HttpProxyMiddleware.cs @@ -158,7 +158,7 @@ namespace FastGithub.HttpServer } // 不使用系统dns - address = await this.domainResolver.ResolveAsync(targetHost); + address = await this.domainResolver.ResolveAnyAsync(targetHost); return new IPEndPoint(address, targetPort); } diff --git a/FastGithub.HttpServer/SshReverseProxyHandler.cs b/FastGithub.HttpServer/SshReverseProxyHandler.cs index 16055e2..37ed22e 100644 --- a/FastGithub.HttpServer/SshReverseProxyHandler.cs +++ b/FastGithub.HttpServer/SshReverseProxyHandler.cs @@ -31,7 +31,7 @@ namespace FastGithub.HttpServer /// public override async Task OnConnectedAsync(ConnectionContext context) { - var address = await this.domainResolver.ResolveAsync(SSH_GITHUB_COM); + var address = await this.domainResolver.ResolveAnyAsync(SSH_GITHUB_COM); using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await socket.ConnectAsync(address, SSH_OVER_HTTPS_PORT); var targetStream = new NetworkStream(socket, ownsSocket: false);