自动解决冲突问题

This commit is contained in:
老九 2021-09-20 10:31:38 +08:00
parent df1ee8cc46
commit a1284a4013
9 changed files with 303 additions and 230 deletions

View File

@ -13,19 +13,47 @@ namespace FastGithub.Dns
sealed class DnsInterceptHostedService : BackgroundService
{
private readonly DnsInterceptor dnsInterceptor;
private readonly IEnumerable<IConflictValidator> conflictValidators;
private readonly IEnumerable<IConflictSolver> conflictSolvers;
/// <summary>
/// dns拦截后台服务
/// </summary>
/// <param name="dnsInterceptor"></param>
/// <param name="conflictValidators"></param>
/// <param name="conflictSolvers"></param>
public DnsInterceptHostedService(
DnsInterceptor dnsInterceptor,
IEnumerable<IConflictValidator> conflictValidators)
IEnumerable<IConflictSolver> conflictSolvers)
{
this.dnsInterceptor = dnsInterceptor;
this.conflictValidators = conflictValidators;
this.conflictSolvers = conflictSolvers;
}
/// <summary>
/// 启动时处理冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override async Task StartAsync(CancellationToken cancellationToken)
{
foreach (var solver in this.conflictSolvers)
{
await solver.SolveAsync(cancellationToken);
}
await base.StartAsync(cancellationToken);
}
/// <summary>
/// 停止时恢复冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override async Task StopAsync(CancellationToken cancellationToken)
{
foreach (var solver in this.conflictSolvers)
{
await solver.RestoreAsync(cancellationToken);
}
await base.StopAsync(cancellationToken);
}
/// <summary>
@ -36,10 +64,6 @@ namespace FastGithub.Dns
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield();
foreach (var item in this.conflictValidators)
{
await item.ValidateAsync();
}
this.dnsInterceptor.Intercept(stoppingToken);
}
}

View File

@ -8,6 +8,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="DNS" Version="6.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="WinDivertSharp" Version="1.4.3.3" />
</ItemGroup>

View File

@ -0,0 +1,92 @@
using FastGithub.Configuration;
using System;
using System.IO;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Dns
{
/// <summary>
/// host文件冲解决者
/// </summary>
[SupportedOSPlatform("windows")]
sealed class HostsConflictSolver : IConflictSolver
{
private readonly FastGithubConfig fastGithubConfig;
/// <summary>
/// host文件冲解决者
/// </summary>
/// <param name="fastGithubConfig"></param>
public HostsConflictSolver(
FastGithubConfig fastGithubConfig)
{
this.fastGithubConfig = fastGithubConfig;
}
/// <summary>
/// 解决冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task SolveAsync(CancellationToken cancellationToken)
{
var hostsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers/etc/hosts");
if (File.Exists(hostsPath) == false)
{
return;
}
var hostsBuilder = new StringBuilder();
var lines = await File.ReadAllLinesAsync(hostsPath, cancellationToken);
foreach (var line in lines)
{
if (this.IsConflictingLine(line))
{
hostsBuilder.AppendLine($"# {line}");
}
else
{
hostsBuilder.AppendLine(line);
}
}
File.SetAttributes(hostsPath, FileAttributes.Normal);
await File.WriteAllTextAsync(hostsPath, hostsBuilder.ToString(), cancellationToken);
}
/// <summary>
/// 恢复冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task RestoreAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// 是否为冲突的行
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
private bool IsConflictingLine(string line)
{
if (line.TrimStart().StartsWith("#"))
{
return false;
}
var items = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (items.Length < 2)
{
return false;
}
var domain = items[1];
return this.fastGithubConfig.IsMatch(domain);
}
}
}

View File

@ -1,117 +0,0 @@
using FastGithub.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace FastGithub.Dns
{
/// <summary>
/// host文件冲突验证器
/// </summary>
sealed class HostsConflictValidator : IConflictValidator
{
private readonly FastGithubConfig fastGithubConfig;
private readonly ILogger<HostsConflictValidator> logger;
/// <summary>
/// host文件冲突验证器
/// </summary>
/// <param name="fastGithubConfig"></param>
/// <param name="logger"></param>
public HostsConflictValidator(
FastGithubConfig fastGithubConfig,
ILogger<HostsConflictValidator> logger)
{
this.fastGithubConfig = fastGithubConfig;
this.logger = logger;
}
/// <summary>
/// 验证冲突
/// </summary>
/// <returns></returns>
public async Task ValidateAsync()
{
var hostsPath = @"/etc/hosts";
if (OperatingSystem.IsWindows())
{
hostsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), $"drivers/{hostsPath}");
}
if (File.Exists(hostsPath) == false)
{
return;
}
var lines = await File.ReadAllLinesAsync(hostsPath);
foreach (var line in lines)
{
if (HostsRecord.TryParse(line, out var record) == false)
{
continue;
}
if (IPAddress.Loopback.Equals(record.Address) == true)
{
continue;
}
if (this.fastGithubConfig.IsMatch(record.Domain))
{
this.logger.LogError($"由于你的hosts文件设置了{record}{nameof(FastGithub)}无法加速此域名");
}
}
}
/// <summary>
/// hosts文件记录
/// </summary>
private class HostsRecord
{
/// <summary>
/// 获取域名
/// </summary>
public string Domain { get; }
/// <summary>
/// 获取地址
/// </summary>
public IPAddress Address { get; }
private HostsRecord(string domain, IPAddress address)
{
this.Domain = domain;
this.Address = address;
}
public override string ToString()
{
return $"[{this.Domain}->{this.Address}]";
}
public static bool TryParse(string record, [MaybeNullWhen(false)] out HostsRecord value)
{
value = null;
if (record.TrimStart().StartsWith("#"))
{
return false;
}
var items = record.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (items.Length < 2)
{
return false;
}
if (IPAddress.TryParse(items[0], out var address) == false)
{
return false;
}
value = new HostsRecord(items[1], address);
return true;
}
}
}
}

View File

@ -0,0 +1,25 @@
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Dns
{
/// <summary>
/// Dns冲突解决者
/// </summary>
interface IConflictSolver
{
/// <summary>
/// 解决冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SolveAsync(CancellationToken cancellationToken);
/// <summary>
/// 恢复冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task RestoreAsync(CancellationToken cancellationToken);
}
}

View File

@ -1,16 +0,0 @@
using System.Threading.Tasks;
namespace FastGithub.Dns
{
/// <summary>
/// Dns冲突验证器
/// </summary>
interface IConflictValidator
{
/// <summary>
/// 验证冲突
/// </summary>
/// <returns></returns>
Task ValidateAsync();
}
}

View File

@ -0,0 +1,151 @@
using FastGithub.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Dns
{
/// <summary>
/// 代理冲突解决者
/// </summary>
[SupportedOSPlatform("windows")]
sealed class ProxyConflictSolver : IConflictSolver
{
private const int INTERNET_OPTION_REFRESH = 37;
private const int INTERNET_OPTION_PROXY_SETTINGS_CHANGED = 95;
private const string PROXYOVERRIDE_KEY = "ProxyOverride";
private const string INTERNET_SETTINGS = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
private readonly IOptions<FastGithubOptions> options;
private readonly ILogger<ProxyConflictSolver> logger;
[DllImport("wininet.dll")]
private static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
/// <summary>
/// 代理冲突解决者
/// </summary>
/// <param name="options"></param>
/// <param name="logger"></param>
public ProxyConflictSolver(
IOptions<FastGithubOptions> options,
ILogger<ProxyConflictSolver> logger)
{
this.options = options;
this.logger = logger;
}
/// <summary>
/// 解决冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task SolveAsync(CancellationToken cancellationToken)
{
this.SetToProxyOvride();
this.CheckProxyConflict();
return Task.CompletedTask;
}
/// <summary>
/// 恢复冲突
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task RestoreAsync(CancellationToken cancellationToken)
{
this.RemoveFromProxyOvride();
return Task.CompletedTask;
}
/// <summary>
/// 添加到ProxyOvride
/// </summary>
private void SetToProxyOvride()
{
using var settings = Registry.CurrentUser.OpenSubKey(INTERNET_SETTINGS, writable: true);
if (settings == null)
{
return;
}
var items = this.options.Value.DomainConfigs.Keys.ToHashSet();
foreach (var item in GetProxyOvride(settings))
{
items.Add(item);
}
SetProxyOvride(settings, items);
}
/// <summary>
/// 从ProxyOvride移除
/// </summary>
private void RemoveFromProxyOvride()
{
using var settings = Registry.CurrentUser.OpenSubKey(INTERNET_SETTINGS, writable: true);
if (settings == null)
{
return;
}
var proxyOvride = GetProxyOvride(settings);
var items = proxyOvride.Except(this.options.Value.DomainConfigs.Keys);
SetProxyOvride(settings, items);
}
/// <summary>
/// 检测代理冲突
/// </summary>
private void CheckProxyConflict()
{
if (HttpClient.DefaultProxy == null)
{
return;
}
foreach (var domain in this.options.Value.DomainConfigs.Keys)
{
var destination = new Uri($"https://{domain.Replace('*', 'a')}");
var proxyServer = HttpClient.DefaultProxy.GetProxy(destination);
if (proxyServer != null)
{
this.logger.LogError($"由于系统配置了{proxyServer}代理{domain}{nameof(FastGithub)}无法加速相关域名");
}
}
}
/// <summary>
/// 获取ProxyOverride
/// </summary>
/// <param name="registryKey"></param>
/// <returns></returns>
private static string[] GetProxyOvride(RegistryKey registryKey)
{
var value = registryKey.GetValue(PROXYOVERRIDE_KEY, null)?.ToString();
return value == null ? Array.Empty<string>() : value.Split(';', StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// 设置ProxyOverride
/// </summary>
/// <param name="registryKey"></param>
/// <param name="items"></param>
private static void SetProxyOvride(RegistryKey registryKey, IEnumerable<string> items)
{
var value = string.Join(';', items);
registryKey.SetValue(PROXYOVERRIDE_KEY, value, RegistryValueKind.String);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
}
}
}

View File

@ -1,87 +0,0 @@
using FastGithub.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace FastGithub.Dns
{
/// <summary>
/// 代理冲突验证
/// </summary>
sealed class ProxyConflictValidtor : IConflictValidator
{
private readonly IOptions<FastGithubOptions> options;
private readonly ILogger<ProxyConflictValidtor> logger;
public ProxyConflictValidtor(
IOptions<FastGithubOptions> options,
ILogger<ProxyConflictValidtor> logger)
{
this.options = options;
this.logger = logger;
}
/// <summary>
/// 验证冲突
/// </summary>
/// <returns></returns>
public async Task ValidateAsync()
{
var systemProxy = HttpClient.DefaultProxy;
if (systemProxy == null)
{
return;
}
var httpProxyPort = this.options.Value.HttpProxyPort;
foreach (var domain in this.options.Value.DomainConfigs.Keys)
{
var destination = new Uri($"https://{domain.Replace('*', 'a')}");
var proxyServer = systemProxy.GetProxy(destination);
if (proxyServer == null)
{
continue;
}
if (await IsFastGithubProxyAsync(proxyServer, httpProxyPort) == false)
{
this.logger.LogError($"由于系统配置了{proxyServer}代理{domain}{nameof(FastGithub)}无法加速相关域名");
}
}
}
/// <summary>
/// 是否为fastgithub代理
/// </summary>
/// <param name="proxyServer"></param>
/// <param name="httpProxyPort"></param>
/// <returns></returns>
private static async Task<bool> IsFastGithubProxyAsync(Uri proxyServer, int httpProxyPort)
{
if (proxyServer.Port != httpProxyPort)
{
return false;
}
if (IPAddress.TryParse(proxyServer.Host, out var address))
{
return address.Equals(IPAddress.Loopback);
}
try
{
var addresses = await System.Net.Dns.GetHostAddressesAsync(proxyServer.Host);
return addresses.Contains(IPAddress.Loopback);
}
catch (Exception)
{
return false;
}
}
}
}

View File

@ -19,8 +19,8 @@ namespace FastGithub
public static IServiceCollection AddDnsInterceptor(this IServiceCollection services)
{
services.TryAddSingleton<DnsInterceptor>();
services.AddSingleton<IConflictValidator, HostsConflictValidator>();
services.AddSingleton<IConflictValidator, ProxyConflictValidtor>();
services.AddSingleton<IConflictSolver, HostsConflictSolver>();
services.AddSingleton<IConflictSolver, ProxyConflictSolver>();
return services.AddHostedService<DnsInterceptHostedService>();
}
}