挑选可用且最快的ip

This commit is contained in:
xljiulang 2021-08-17 23:23:18 +08:00
parent 15b31e6fa5
commit 4faa241b87
5 changed files with 92 additions and 44 deletions

View File

@ -18,19 +18,14 @@ namespace FastGithub.Dns
{
private readonly TimeSpan ttl = TimeSpan.FromMinutes(1d);
private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<RequestResolver> logger;
/// <summary>
/// dns解析者
/// </summary>
/// <param name="fastGithubConfig"></param>
/// <param name="logger"></param>
public RequestResolver(
FastGithubConfig fastGithubConfig,
ILogger<RequestResolver> logger)
public RequestResolver(FastGithubConfig fastGithubConfig)
{
this.fastGithubConfig = fastGithubConfig;
this.logger = logger;
}
/// <summary>
@ -60,8 +55,6 @@ namespace FastGithub.Dns
var localAddress = remoteEndPointRequest.GetLocalIPAddress() ?? IPAddress.Loopback;
var record = new IPAddressResourceRecord(domain, localAddress, this.ttl);
response.AnswerRecords.Add(record);
this.logger.LogInformation($"[{domain}->{localAddress}]");
return response;
}

View File

@ -4,8 +4,11 @@ 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;
@ -19,7 +22,11 @@ namespace FastGithub.DomainResolve
private readonly IMemoryCache memoryCache;
private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<DomainResolver> logger;
private readonly TimeSpan cacheTimeSpan = TimeSpan.FromSeconds(10d);
private readonly TimeSpan lookupTimeout = TimeSpan.FromSeconds(2d);
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(2d);
private readonly TimeSpan resolveCacheTimeSpan = TimeSpan.FromMinutes(2d);
private readonly ConcurrentDictionary<DnsEndPoint, SemaphoreSlim> semaphoreSlims = new();
/// <summary>
/// 域名解析器
@ -38,42 +45,49 @@ namespace FastGithub.DomainResolve
}
/// <summary>
/// 解析指定的域名
/// 解析域名
/// </summary>
/// <param name="domain"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="FastGithubException"></exception>
public Task<IPAddress> ResolveAsync(string domain, CancellationToken cancellationToken)
public async Task<IPAddress> ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default)
{
// 缓存以避免做不必要的并发查询
var key = $"{nameof(DomainResolver)}:{domain}";
return this.memoryCache.GetOrCreateAsync(key, e =>
var semaphore = this.semaphoreSlims.GetOrAdd(endPoint, _ => new SemaphoreSlim(1, 1));
try
{
e.SetAbsoluteExpiration(this.cacheTimeSpan);
return this.LookupAsync(domain, cancellationToken);
});
await semaphore.WaitAsync(cancellationToken);
if (this.memoryCache.TryGetValue<IPAddress>(endPoint, out var address) == false)
{
address = await this.LookupAsync(endPoint, cancellationToken);
this.memoryCache.Set(endPoint, address, this.resolveCacheTimeSpan);
}
return address;
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 查找ip
/// </summary>
/// <param name="domain"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="FastGithubException">
private async Task<IPAddress> LookupAsync(string domain, CancellationToken cancellationToken)
private async Task<IPAddress> LookupAsync(DnsEndPoint endPoint, CancellationToken cancellationToken)
{
var pureDns = this.fastGithubConfig.PureDns;
var fastDns = this.fastGithubConfig.FastDns;
try
{
return await LookupCoreAsync(pureDns, domain, cancellationToken);
return await LookupCoreAsync(pureDns, endPoint, cancellationToken);
}
catch (Exception)
{
this.logger.LogWarning($"由于{pureDns}解析{domain}失败,本次使用{fastDns}");
return await LookupCoreAsync(fastDns, domain, cancellationToken);
this.logger.LogWarning($"由于{pureDns}解析{endPoint.Host}失败,本次使用{fastDns}");
return await LookupCoreAsync(fastDns, endPoint, cancellationToken);
}
}
@ -81,24 +95,66 @@ namespace FastGithub.DomainResolve
/// 查找ip
/// </summary>
/// <param name="dns"></param>
/// <param name="domain"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<IPAddress> LookupCoreAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken)
private async Task<IPAddress> LookupCoreAsync(IPEndPoint dns, DnsEndPoint endPoint, CancellationToken cancellationToken)
{
var dnsClient = new DnsClient(dns);
using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2d));
using var timeoutTokenSource = new CancellationTokenSource(this.lookupTimeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
var addresses = await dnsClient.Lookup(domain, RecordType.A, linkedTokenSource.Token);
var address = addresses?.FirstOrDefault();
if (address == null)
var addresses = await dnsClient.Lookup(endPoint.Host, RecordType.A, linkedTokenSource.Token);
var fastAddress = await this.GetFastIPAddressAsync(addresses, endPoint.Port, cancellationToken);
if (fastAddress != null)
{
throw new FastGithubException($"dns{dns}解析不到{domain}的ip");
this.logger.LogInformation($"[{endPoint.Host}->{fastAddress}]");
return fastAddress;
}
throw new FastGithubException($"dns{dns}解析不到{endPoint.Host}可用的ip");
}
this.logger.LogInformation($"[{domain}->{address}]");
/// <summary>
/// 获取最快的ip
/// </summary>
/// <param name="addresses"></param>
/// <param name="port"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<IPAddress?> GetFastIPAddressAsync(IEnumerable<IPAddress> addresses, int port, CancellationToken cancellationToken)
{
var tasks = addresses.Select(address => this.IsAvailableAsync(address, port, cancellationToken));
var fastTask = await Task.WhenAny(tasks);
return await fastTask;
}
/// <summary>
/// 验证远程节点是否可连接
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<IPAddress?> 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;
}
}
}
}

View File

@ -12,9 +12,9 @@ namespace FastGithub.DomainResolve
/// <summary>
/// 解析域名
/// </summary>
/// <param name="domain"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IPAddress> ResolveAsync(string domain, CancellationToken cancellationToken);
Task<IPAddress> ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default);
}
}

View File

@ -67,7 +67,8 @@ namespace FastGithub.Http
{
if (IPAddress.TryParse(this.domainConfig.IPAddress, out var address) == false)
{
address = await this.domainResolver.ResolveAsync(context.Domain, cancellationToken);
var endPoint = new DnsEndPoint(uri.Host, uri.Port);
address = await this.domainResolver.ResolveAsync(endPoint, cancellationToken);
}
uriBuilder.Host = address.ToString();
request.Headers.Host = context.Domain;

View File

@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Connections;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
@ -13,9 +12,8 @@ namespace FastGithub.ReverseProxy
/// </summary>
sealed class GithubSshHandler : ConnectionHandler
{
private const int SSH_OVER_HTTPS_PORT = 443;
private const string SSH_GITHUB_COM = "ssh.github.com";
private readonly IDomainResolver domainResolver;
private readonly DnsEndPoint githubSshEndPoint = new("ssh.github.com", 443);
/// <summary>
/// github的ssh处理者
@ -33,9 +31,9 @@ namespace FastGithub.ReverseProxy
/// <returns></returns>
public override async Task OnConnectedAsync(ConnectionContext connection)
{
var address = await this.domainResolver.ResolveAsync(SSH_GITHUB_COM, CancellationToken.None);
var address = await this.domainResolver.ResolveAsync(this.githubSshEndPoint);
using var socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(new IPEndPoint(address, SSH_OVER_HTTPS_PORT));
await socket.ConnectAsync(address, this.githubSshEndPoint.Port);
var targetStream = new NetworkStream(socket, ownsSocket: false);
var task1 = targetStream.CopyToAsync(connection.Transport.Output);