commit
e8c5fd5216
@ -7,7 +7,7 @@ namespace FastGithub.Configuration
|
|||||||
/// 表示域名表达式
|
/// 表示域名表达式
|
||||||
/// *表示除.之外任意0到多个字符
|
/// *表示除.之外任意0到多个字符
|
||||||
/// </summary>
|
/// </summary>
|
||||||
sealed class DomainPattern : IComparable<DomainPattern>
|
public class DomainPattern : IComparable<DomainPattern>
|
||||||
{
|
{
|
||||||
private readonly Regex regex;
|
private readonly Regex regex;
|
||||||
private readonly string domainPattern;
|
private readonly string domainPattern;
|
||||||
|
|||||||
@ -117,9 +117,9 @@ namespace FastGithub.Configuration
|
|||||||
/// 获取所有域名表达式
|
/// 获取所有域名表达式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string[] GetDomainPatterns()
|
public DomainPattern[] GetDomainPatterns()
|
||||||
{
|
{
|
||||||
return this.domainConfigs.Keys.Select(item => item.ToString()).ToArray();
|
return this.domainConfigs.Keys.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,16 @@
|
|||||||
using DNS.Client.RequestResolver;
|
using DNS.Client.RequestResolver;
|
||||||
using DNS.Protocol;
|
using DNS.Protocol;
|
||||||
using DNS.Protocol.ResourceRecords;
|
using DNS.Protocol.ResourceRecords;
|
||||||
|
using FastGithub.Configuration;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -21,22 +24,72 @@ namespace FastGithub.DomainResolve
|
|||||||
{
|
{
|
||||||
private const int DNS_PORT = 53;
|
private const int DNS_PORT = 53;
|
||||||
private const string LOCALHOST = "localhost";
|
private const string LOCALHOST = "localhost";
|
||||||
|
|
||||||
|
private readonly DnscryptProxy dnscryptProxy;
|
||||||
|
private readonly FastGithubConfig fastGithubConfig;
|
||||||
private readonly ILogger<DnsClient> logger;
|
private readonly ILogger<DnsClient> logger;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> semaphoreSlims = new();
|
private readonly ConcurrentDictionary<string, SemaphoreSlim> semaphoreSlims = new();
|
||||||
private readonly IMemoryCache dnsCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
|
private readonly IMemoryCache dnsCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
|
||||||
private readonly TimeSpan dnsExpiration = TimeSpan.FromMinutes(2d);
|
private readonly TimeSpan dnsExpiration = TimeSpan.FromMinutes(1d);
|
||||||
private readonly int resolveTimeout = (int)TimeSpan.FromSeconds(2d).TotalMilliseconds;
|
private readonly int resolveTimeout = (int)TimeSpan.FromSeconds(2d).TotalMilliseconds;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DNS客户端
|
/// DNS客户端
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="dnscryptProxy"></param>
|
||||||
|
/// <param name="fastGithubConfig"></param>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger"></param>
|
||||||
public DnsClient(ILogger<DnsClient> logger)
|
public DnsClient(
|
||||||
|
DnscryptProxy dnscryptProxy,
|
||||||
|
FastGithubConfig fastGithubConfig,
|
||||||
|
ILogger<DnsClient> logger)
|
||||||
{
|
{
|
||||||
|
this.dnscryptProxy = dnscryptProxy;
|
||||||
|
this.fastGithubConfig = fastGithubConfig;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析域名
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain">域名</param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async IAsyncEnumerable<IPAddress> ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var hashSet = new HashSet<IPAddress>();
|
||||||
|
foreach (var dns in this.GetDnsServers())
|
||||||
|
{
|
||||||
|
foreach (var address in await this.LookupAsync(dns, domain, cancellationToken))
|
||||||
|
{
|
||||||
|
if (hashSet.Add(address) == true)
|
||||||
|
{
|
||||||
|
yield return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取dns服务
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private IEnumerable<IPEndPoint> GetDnsServers()
|
||||||
|
{
|
||||||
|
var cryptDns = this.dnscryptProxy.LocalEndPoint;
|
||||||
|
if (cryptDns != null)
|
||||||
|
{
|
||||||
|
yield return cryptDns;
|
||||||
|
yield return cryptDns;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
|
||||||
|
{
|
||||||
|
yield return fallbackDns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析域名
|
/// 解析域名
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -44,7 +97,7 @@ namespace FastGithub.DomainResolve
|
|||||||
/// <param name="domain"></param>
|
/// <param name="domain"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IPAddress[]> LookupAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
|
private async Task<IPAddress[]> LookupAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var key = $"{dns}:{domain}";
|
var key = $"{dns}:{domain}";
|
||||||
var semaphore = this.semaphoreSlims.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
var semaphore = this.semaphoreSlims.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
||||||
@ -96,10 +149,16 @@ namespace FastGithub.DomainResolve
|
|||||||
RecursionDesired = true,
|
RecursionDesired = true,
|
||||||
OperationCode = OperationCode.Query
|
OperationCode = OperationCode.Query
|
||||||
};
|
};
|
||||||
|
|
||||||
request.Questions.Add(new Question(new Domain(domain), RecordType.A));
|
request.Questions.Add(new Question(new Domain(domain), RecordType.A));
|
||||||
var clientRequest = new ClientRequest(resolver, request);
|
var clientRequest = new ClientRequest(resolver, request);
|
||||||
var response = await clientRequest.Resolve(cancellationToken);
|
var response = await clientRequest.Resolve(cancellationToken);
|
||||||
return response.AnswerRecords.OfType<IPAddressResourceRecord>().Select(item => item.IPAddress).ToArray();
|
|
||||||
|
return response.AnswerRecords
|
||||||
|
.OfType<IPAddressResourceRecord>()
|
||||||
|
.Where(item => IPAddress.IsLoopback(item.IPAddress) == false)
|
||||||
|
.Select(item => item.IPAddress)
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using FastGithub.Configuration;
|
using FastGithub.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -20,6 +21,8 @@ namespace FastGithub.DomainResolve
|
|||||||
private const string PATH = "dnscrypt-proxy";
|
private const string PATH = "dnscrypt-proxy";
|
||||||
private const string NAME = "dnscrypt-proxy";
|
private const string NAME = "dnscrypt-proxy";
|
||||||
|
|
||||||
|
private readonly ILogger<DnscryptProxy> logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 相关进程
|
/// 相关进程
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -30,12 +33,38 @@ namespace FastGithub.DomainResolve
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IPEndPoint? LocalEndPoint { get; private set; }
|
public IPEndPoint? LocalEndPoint { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DnscryptProxy服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger"></param>
|
||||||
|
public DnscryptProxy(ILogger<DnscryptProxy> logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动dnscrypt-proxy
|
/// 启动dnscrypt-proxy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.StartCoreAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning($"{NAME}启动失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动dnscrypt-proxy
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task StartCoreAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tomlPath = Path.Combine(PATH, $"{NAME}.toml");
|
var tomlPath = Path.Combine(PATH, $"{NAME}.toml");
|
||||||
var port = GetAvailablePort(IPAddress.Loopback.AddressFamily);
|
var port = GetAvailablePort(IPAddress.Loopback.AddressFamily);
|
||||||
@ -70,7 +99,32 @@ namespace FastGithub.DomainResolve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 停止服务
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
StartDnscryptProxy("-service stop")?.WaitForExit();
|
||||||
|
StartDnscryptProxy("-service uninstall")?.WaitForExit();
|
||||||
|
}
|
||||||
|
if (this.process != null && this.process.HasExited == false)
|
||||||
|
{
|
||||||
|
this.process.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning($"{NAME}停止失败:{ex.Message }");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.LocalEndPoint = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取可用的随机端口
|
/// 获取可用的随机端口
|
||||||
@ -113,24 +167,6 @@ namespace FastGithub.DomainResolve
|
|||||||
this.LocalEndPoint = null;
|
this.LocalEndPoint = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 停止dnscrypt-proxy
|
|
||||||
/// </summary>
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
StartDnscryptProxy("-service stop")?.WaitForExit();
|
|
||||||
StartDnscryptProxy("-service uninstall")?.WaitForExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.process != null && this.process.HasExited == false)
|
|
||||||
{
|
|
||||||
this.process.Kill();
|
|
||||||
}
|
|
||||||
this.LocalEndPoint = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动DnscryptProxy进程
|
/// 启动DnscryptProxy进程
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -7,58 +6,54 @@ using System.Threading.Tasks;
|
|||||||
namespace FastGithub.DomainResolve
|
namespace FastGithub.DomainResolve
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DnscryptProxy后台服务
|
/// 域名解析后台服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
sealed class DnscryptProxyHostedService : BackgroundService
|
sealed class DomainResolveHostedService : BackgroundService
|
||||||
{
|
{
|
||||||
private readonly ILogger<DnscryptProxyHostedService> logger;
|
|
||||||
private readonly DnscryptProxy dnscryptProxy;
|
private readonly DnscryptProxy dnscryptProxy;
|
||||||
|
private readonly DomainSpeedTester speedTester;
|
||||||
|
|
||||||
|
private readonly TimeSpan speedTestDueTime = TimeSpan.FromSeconds(10d);
|
||||||
|
private readonly TimeSpan speedTestPeriod = TimeSpan.FromMinutes(2d);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DnscryptProxy后台服务
|
/// 域名解析后台服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dnscryptProxy"></param>
|
/// <param name="dnscryptProxy"></param>
|
||||||
/// <param name="logger"></param>
|
/// <param name="speedTester"></param>
|
||||||
public DnscryptProxyHostedService(
|
public DomainResolveHostedService(
|
||||||
DnscryptProxy dnscryptProxy,
|
DnscryptProxy dnscryptProxy,
|
||||||
ILogger<DnscryptProxyHostedService> logger)
|
DomainSpeedTester speedTester)
|
||||||
{
|
{
|
||||||
this.dnscryptProxy = dnscryptProxy;
|
this.dnscryptProxy = dnscryptProxy;
|
||||||
this.logger = logger;
|
this.speedTester = speedTester;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动dnscrypt-proxy
|
/// 后台任务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="stoppingToken"></param>
|
/// <param name="stoppingToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
try
|
await this.dnscryptProxy.StartAsync(stoppingToken);
|
||||||
|
await Task.Delay(this.speedTestDueTime, stoppingToken);
|
||||||
|
|
||||||
|
while (stoppingToken.IsCancellationRequested == false)
|
||||||
{
|
{
|
||||||
await this.dnscryptProxy.StartAsync(stoppingToken);
|
await this.speedTester.TestSpeedAsync(stoppingToken);
|
||||||
}
|
await Task.Delay(this.speedTestPeriod, stoppingToken);
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.logger.LogWarning($"{this.dnscryptProxy}启动失败:{ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 停止dnscrypt-proxy
|
/// 停止服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public override Task StopAsync(CancellationToken cancellationToken)
|
public override Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
this.dnscryptProxy.Stop();
|
||||||
{
|
|
||||||
this.dnscryptProxy.Stop();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.logger.LogWarning($"{this.dnscryptProxy}停止失败:{ex.Message}");
|
|
||||||
}
|
|
||||||
return base.StopAsync(cancellationToken);
|
return base.StopAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12,24 +12,20 @@ namespace FastGithub.DomainResolve
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
sealed class DomainResolver : IDomainResolver
|
sealed class DomainResolver : IDomainResolver
|
||||||
{
|
{
|
||||||
private readonly DnscryptProxy dnscryptProxy;
|
|
||||||
private readonly FastGithubConfig fastGithubConfig;
|
|
||||||
private readonly DnsClient dnsClient;
|
private readonly DnsClient dnsClient;
|
||||||
|
private readonly DomainSpeedTester speedTester;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 域名解析器
|
/// 域名解析器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dnscryptProxy"></param>
|
|
||||||
/// <param name="fastGithubConfig"></param>
|
|
||||||
/// <param name="dnsClient"></param>
|
/// <param name="dnsClient"></param>
|
||||||
|
/// <param name="speedTester"></param>
|
||||||
public DomainResolver(
|
public DomainResolver(
|
||||||
DnscryptProxy dnscryptProxy,
|
DnsClient dnsClient,
|
||||||
FastGithubConfig fastGithubConfig,
|
DomainSpeedTester speedTester)
|
||||||
DnsClient dnsClient)
|
|
||||||
{
|
{
|
||||||
this.dnscryptProxy = dnscryptProxy;
|
|
||||||
this.fastGithubConfig = fastGithubConfig;
|
|
||||||
this.dnsClient = dnsClient;
|
this.dnsClient = dnsClient;
|
||||||
|
this.speedTester = speedTester;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -55,35 +51,21 @@ namespace FastGithub.DomainResolve
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async IAsyncEnumerable<IPAddress> ResolveAllAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
|
public async IAsyncEnumerable<IPAddress> ResolveAllAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var hashSet = new HashSet<IPAddress>();
|
var addresses = this.speedTester.GetIPAddresses(domain);
|
||||||
foreach (var dns in this.GetDnsServers())
|
if (addresses.Length > 0)
|
||||||
{
|
{
|
||||||
foreach (var address in await this.dnsClient.LookupAsync(dns, domain, cancellationToken))
|
foreach (var address in addresses)
|
||||||
{
|
{
|
||||||
if (hashSet.Add(address) == true)
|
yield return address;
|
||||||
{
|
|
||||||
yield return address;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取dns服务
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private IEnumerable<IPEndPoint> GetDnsServers()
|
|
||||||
{
|
|
||||||
var cryptDns = this.dnscryptProxy.LocalEndPoint;
|
|
||||||
if (cryptDns != null)
|
|
||||||
{
|
{
|
||||||
yield return cryptDns;
|
this.speedTester.Add(domain);
|
||||||
yield return cryptDns;
|
await foreach (var address in this.dnsClient.ResolveAsync(domain, cancellationToken))
|
||||||
}
|
{
|
||||||
|
yield return address;
|
||||||
foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
|
}
|
||||||
{
|
|
||||||
yield return fallbackDns;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
151
FastGithub.DomainResolve/DomainSpeedTester.cs
Normal file
151
FastGithub.DomainResolve/DomainSpeedTester.cs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
using FastGithub.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FastGithub.DomainResolve
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 域名的IP测速服务
|
||||||
|
/// </summary>
|
||||||
|
sealed class DomainSpeedTester
|
||||||
|
{
|
||||||
|
private const string DOMAINS_JSON_FILE = "domains.json";
|
||||||
|
|
||||||
|
private readonly DnsClient dnsClient;
|
||||||
|
private readonly ILogger<DomainSpeedTester> logger;
|
||||||
|
|
||||||
|
private readonly object syncRoot = new();
|
||||||
|
private readonly Dictionary<string, IPAddressItemHashSet> domainIPAddressHashSet = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 域名的IP测速服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dnsClient"></param>
|
||||||
|
/// <param name="logger"></param>
|
||||||
|
public DomainSpeedTester(
|
||||||
|
DnsClient dnsClient,
|
||||||
|
ILogger<DomainSpeedTester> logger)
|
||||||
|
{
|
||||||
|
this.dnsClient = dnsClient;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LoadDomains();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogWarning($"加载域名数据失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载域名数据
|
||||||
|
/// </summary>
|
||||||
|
private void LoadDomains()
|
||||||
|
{
|
||||||
|
if (File.Exists(DOMAINS_JSON_FILE) == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var utf8Json = File.ReadAllBytes(DOMAINS_JSON_FILE);
|
||||||
|
var domains = JsonSerializer.Deserialize<string[]>(utf8Json);
|
||||||
|
if (domains == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var domain in domains)
|
||||||
|
{
|
||||||
|
this.domainIPAddressHashSet.TryAdd(domain, new IPAddressItemHashSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加要测速的域名
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain"></param>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保存域名
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取测试后排序的IP
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IPAddress[] GetIPAddresses(string domain)
|
||||||
|
{
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
if (this.domainIPAddressHashSet.TryGetValue(domain, out var hashSet) && hashSet.Count > 0)
|
||||||
|
{
|
||||||
|
return hashSet.ToArray().OrderBy(item => item.PingElapsed).Select(item => item.Address).ToArray();
|
||||||
|
}
|
||||||
|
return Array.Empty<IPAddress>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 进行一轮IP测速
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task TestSpeedAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
KeyValuePair<string, IPAddressItemHashSet>[] keyValues;
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
keyValues = this.domainIPAddressHashSet.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var keyValue in keyValues)
|
||||||
|
{
|
||||||
|
var domain = keyValue.Key;
|
||||||
|
var hashSet = keyValue.Value;
|
||||||
|
await foreach (var address in this.dnsClient.ResolveAsync(domain, cancellationToken))
|
||||||
|
{
|
||||||
|
hashSet.Add(new IPAddressItem(address));
|
||||||
|
}
|
||||||
|
await hashSet.PingAllAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,4 +17,16 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="domains.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="domains.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
70
FastGithub.DomainResolve/IPAddressItem.cs
Normal file
70
FastGithub.DomainResolve/IPAddressItem.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FastGithub.DomainResolve
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IP地址项
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("Address = {Address}, PingElapsed = {PingElapsed}")]
|
||||||
|
sealed class IPAddressItem : IEquatable<IPAddressItem>
|
||||||
|
{
|
||||||
|
private readonly Ping ping = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 地址
|
||||||
|
/// </summary>
|
||||||
|
public IPAddress Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ping耗时
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan PingElapsed { get; private set; } = TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IP地址项
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
public IPAddressItem(IPAddress address)
|
||||||
|
{
|
||||||
|
this.Address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发起ping请求
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
FastGithub.DomainResolve/IPAddressItemHashSet.cs
Normal file
64
FastGithub.DomainResolve/IPAddressItemHashSet.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FastGithub.DomainResolve
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IPAddressItem集合
|
||||||
|
/// </summary>
|
||||||
|
sealed class IPAddressItemHashSet
|
||||||
|
{
|
||||||
|
private readonly object syncRoot = new();
|
||||||
|
private readonly HashSet<IPAddressItem> hashSet = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取元素数量
|
||||||
|
/// </summary>
|
||||||
|
public int Count => this.hashSet.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加元素
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Add(IPAddressItem item)
|
||||||
|
{
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
return this.hashSet.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 转换为数组
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IPAddressItem[] ToArray()
|
||||||
|
{
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
return this.hashSet.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ping所有IP
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,8 +18,10 @@ namespace FastGithub
|
|||||||
{
|
{
|
||||||
services.TryAddSingleton<DnsClient>();
|
services.TryAddSingleton<DnsClient>();
|
||||||
services.TryAddSingleton<DnscryptProxy>();
|
services.TryAddSingleton<DnscryptProxy>();
|
||||||
|
services.TryAddSingleton<DomainSpeedTester>();
|
||||||
services.TryAddSingleton<IDomainResolver, DomainResolver>();
|
services.TryAddSingleton<IDomainResolver, DomainResolver>();
|
||||||
return services.AddHostedService<DnscryptProxyHostedService>();
|
services.AddHostedService<DomainResolveHostedService>();
|
||||||
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
FastGithub.DomainResolve/domains.json
Normal file
22
FastGithub.DomainResolve/domains.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
"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"
|
||||||
|
]
|
||||||
Loading…
Reference in New Issue
Block a user