diff --git a/FastGithub.Dns/DnsInterceptHostedService.cs b/FastGithub.Dns/DnsInterceptHostedService.cs index 29baf14..c259f98 100644 --- a/FastGithub.Dns/DnsInterceptHostedService.cs +++ b/FastGithub.Dns/DnsInterceptHostedService.cs @@ -13,19 +13,47 @@ namespace FastGithub.Dns sealed class DnsInterceptHostedService : BackgroundService { private readonly DnsInterceptor dnsInterceptor; - private readonly IEnumerable conflictValidators; + private readonly IEnumerable conflictSolvers; /// /// dns拦截后台服务 /// /// - /// + /// public DnsInterceptHostedService( DnsInterceptor dnsInterceptor, - IEnumerable conflictValidators) + IEnumerable conflictSolvers) { this.dnsInterceptor = dnsInterceptor; - this.conflictValidators = conflictValidators; + this.conflictSolvers = conflictSolvers; + } + + /// + /// 启动时处理冲突 + /// + /// + /// + public override async Task StartAsync(CancellationToken cancellationToken) + { + foreach (var solver in this.conflictSolvers) + { + await solver.SolveAsync(cancellationToken); + } + await base.StartAsync(cancellationToken); + } + + /// + /// 停止时恢复冲突 + /// + /// + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + foreach (var solver in this.conflictSolvers) + { + await solver.RestoreAsync(cancellationToken); + } + await base.StopAsync(cancellationToken); } /// @@ -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); } } diff --git a/FastGithub.Dns/FastGithub.Dns.csproj b/FastGithub.Dns/FastGithub.Dns.csproj index 0a31447..08a71e8 100644 --- a/FastGithub.Dns/FastGithub.Dns.csproj +++ b/FastGithub.Dns/FastGithub.Dns.csproj @@ -8,6 +8,7 @@ + diff --git a/FastGithub.Dns/HostsConflictSolver.cs b/FastGithub.Dns/HostsConflictSolver.cs new file mode 100644 index 0000000..58b07d6 --- /dev/null +++ b/FastGithub.Dns/HostsConflictSolver.cs @@ -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 +{ + /// + /// host文件冲解决者 + /// + [SupportedOSPlatform("windows")] + sealed class HostsConflictSolver : IConflictSolver + { + private readonly FastGithubConfig fastGithubConfig; + + /// + /// host文件冲解决者 + /// + /// + public HostsConflictSolver( + FastGithubConfig fastGithubConfig) + { + this.fastGithubConfig = fastGithubConfig; + } + + /// + /// 解决冲突 + /// + /// + /// + 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); + } + + /// + /// 恢复冲突 + /// + /// + /// + public Task RestoreAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + /// + /// 是否为冲突的行 + /// + /// + /// + 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); + } + } +} diff --git a/FastGithub.Dns/HostsConflictValidator.cs b/FastGithub.Dns/HostsConflictValidator.cs deleted file mode 100644 index 5de91e2..0000000 --- a/FastGithub.Dns/HostsConflictValidator.cs +++ /dev/null @@ -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 -{ - /// - /// host文件冲突验证器 - /// - sealed class HostsConflictValidator : IConflictValidator - { - private readonly FastGithubConfig fastGithubConfig; - private readonly ILogger logger; - - /// - /// host文件冲突验证器 - /// - /// - /// - public HostsConflictValidator( - FastGithubConfig fastGithubConfig, - ILogger logger) - { - this.fastGithubConfig = fastGithubConfig; - this.logger = logger; - } - - /// - /// 验证冲突 - /// - /// - 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)}无法加速此域名"); - } - } - } - - /// - /// hosts文件记录 - /// - private class HostsRecord - { - /// - /// 获取域名 - /// - public string Domain { get; } - - /// - /// 获取地址 - /// - 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; - } - } - } -} diff --git a/FastGithub.Dns/IConflictSolver.cs b/FastGithub.Dns/IConflictSolver.cs new file mode 100644 index 0000000..b72f2a3 --- /dev/null +++ b/FastGithub.Dns/IConflictSolver.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace FastGithub.Dns +{ + /// + /// Dns冲突解决者 + /// + interface IConflictSolver + { + /// + /// 解决冲突 + /// + /// + /// + Task SolveAsync(CancellationToken cancellationToken); + + /// + /// 恢复冲突 + /// + /// + /// + Task RestoreAsync(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/FastGithub.Dns/IConflictValidator.cs b/FastGithub.Dns/IConflictValidator.cs deleted file mode 100644 index a380c7e..0000000 --- a/FastGithub.Dns/IConflictValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; - -namespace FastGithub.Dns -{ - /// - /// Dns冲突验证器 - /// - interface IConflictValidator - { - /// - /// 验证冲突 - /// - /// - Task ValidateAsync(); - } -} \ No newline at end of file diff --git a/FastGithub.Dns/ProxyConflictSolver.cs b/FastGithub.Dns/ProxyConflictSolver.cs new file mode 100644 index 0000000..c50ac73 --- /dev/null +++ b/FastGithub.Dns/ProxyConflictSolver.cs @@ -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 +{ + /// + /// 代理冲突解决者 + /// + [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 options; + private readonly ILogger logger; + + [DllImport("wininet.dll")] + private static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength); + + + /// + /// 代理冲突解决者 + /// + /// + /// + public ProxyConflictSolver( + IOptions options, + ILogger logger) + { + this.options = options; + this.logger = logger; + } + + /// + /// 解决冲突 + /// + /// + /// + public Task SolveAsync(CancellationToken cancellationToken) + { + this.SetToProxyOvride(); + this.CheckProxyConflict(); + return Task.CompletedTask; + } + + /// + /// 恢复冲突 + /// + /// + /// + public Task RestoreAsync(CancellationToken cancellationToken) + { + this.RemoveFromProxyOvride(); + return Task.CompletedTask; + } + + /// + /// 添加到ProxyOvride + /// + 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); + } + + /// + /// 从ProxyOvride移除 + /// + 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); + } + + /// + /// 检测代理冲突 + /// + 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)}无法加速相关域名"); + } + } + } + + /// + /// 获取ProxyOverride + /// + /// + /// + private static string[] GetProxyOvride(RegistryKey registryKey) + { + var value = registryKey.GetValue(PROXYOVERRIDE_KEY, null)?.ToString(); + return value == null ? Array.Empty() : value.Split(';', StringSplitOptions.RemoveEmptyEntries); + } + + /// + /// 设置ProxyOverride + /// + /// + /// + private static void SetProxyOvride(RegistryKey registryKey, IEnumerable 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); + } + } +} diff --git a/FastGithub.Dns/ProxyConflictValidtor.cs b/FastGithub.Dns/ProxyConflictValidtor.cs deleted file mode 100644 index 4f7409b..0000000 --- a/FastGithub.Dns/ProxyConflictValidtor.cs +++ /dev/null @@ -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 -{ - /// - /// 代理冲突验证 - /// - sealed class ProxyConflictValidtor : IConflictValidator - { - private readonly IOptions options; - private readonly ILogger logger; - - public ProxyConflictValidtor( - IOptions options, - ILogger logger) - { - this.options = options; - this.logger = logger; - } - - /// - /// 验证冲突 - /// - /// - 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)}无法加速相关域名"); - } - } - } - - /// - /// 是否为fastgithub代理 - /// - /// - /// - /// - private static async Task 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; - } - } - } -} diff --git a/FastGithub.Dns/ServiceCollectionExtensions.cs b/FastGithub.Dns/ServiceCollectionExtensions.cs index 80dff07..f4736dc 100644 --- a/FastGithub.Dns/ServiceCollectionExtensions.cs +++ b/FastGithub.Dns/ServiceCollectionExtensions.cs @@ -19,8 +19,8 @@ namespace FastGithub public static IServiceCollection AddDnsInterceptor(this IServiceCollection services) { services.TryAddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); return services.AddHostedService(); } }