From 9c2774dfa4d798352a5db42a81a8ae3fbcae8678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=9B=BD=E4=BC=9F?= <366193849@qq.com> Date: Tue, 14 Sep 2021 17:55:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6http=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FastGithub.Configuration/FastGithubOptions.cs | 5 + FastGithub.Configuration/LocalMachine.cs | 2 +- .../DnsDnsPoisoningHostedService.cs | 51 +++++++ ...nsOverHttpsApplicationBuilderExtensions.cs | 23 --- FastGithub.Dns/DnsOverHttpsMiddleware.cs | 129 ---------------- FastGithub.Dns/DnsOverUdpHostedService.cs | 107 ------------- FastGithub.Dns/DnsOverUdpServer.cs | 141 ------------------ FastGithub.Dns/DnsPoisoningServer.cs | 9 +- FastGithub.Dns/RemoteEndPointRequest.cs | 37 ----- FastGithub.Dns/RequestResolver.cs | 80 ---------- FastGithub.Dns/ServiceCollectionExtensions.cs | 13 +- FastGithub.Dns/SystemDnsUtil.cs | 89 ----------- .../ApplicationBuilderExtensions.cs | 15 +- .../HttpProxyMiddleware.cs | 112 ++++++++++++++ ...eware.cs => HttpReverseProxyMiddleware.cs} | 8 +- .../KestrelServerOptionsExtensions.cs | 42 ++++-- FastGithub.ReverseProxy/PortService.cs | 48 ++++++ .../ServiceCollectionExtensions.cs | 6 +- ...xyHandler.cs => SshReverseProxyHandler.cs} | 4 +- FastGithub/Program.cs | 14 +- FastGithub/Startup.cs | 45 +++--- FastGithub/appsettings.json | 1 + 22 files changed, 318 insertions(+), 663 deletions(-) create mode 100644 FastGithub.Dns/DnsDnsPoisoningHostedService.cs delete mode 100644 FastGithub.Dns/DnsOverHttpsApplicationBuilderExtensions.cs delete mode 100644 FastGithub.Dns/DnsOverHttpsMiddleware.cs delete mode 100644 FastGithub.Dns/DnsOverUdpHostedService.cs delete mode 100644 FastGithub.Dns/DnsOverUdpServer.cs delete mode 100644 FastGithub.Dns/RemoteEndPointRequest.cs delete mode 100644 FastGithub.Dns/RequestResolver.cs delete mode 100644 FastGithub.Dns/SystemDnsUtil.cs create mode 100644 FastGithub.ReverseProxy/HttpProxyMiddleware.cs rename FastGithub.ReverseProxy/{ReverseProxyMiddleware.cs => HttpReverseProxyMiddleware.cs} (94%) create mode 100644 FastGithub.ReverseProxy/PortService.cs rename FastGithub.ReverseProxy/{GithubSshProxyHandler.cs => SshReverseProxyHandler.cs} (91%) diff --git a/FastGithub.Configuration/FastGithubOptions.cs b/FastGithub.Configuration/FastGithubOptions.cs index 69faac7..036f0ed 100644 --- a/FastGithub.Configuration/FastGithubOptions.cs +++ b/FastGithub.Configuration/FastGithubOptions.cs @@ -8,6 +8,11 @@ namespace FastGithub.Configuration /// public class FastGithubOptions { + /// + /// http代理端口 + /// + public int HttpProxyPort { get; set; } + /// /// 回退的dns /// diff --git a/FastGithub.Configuration/LocalMachine.cs b/FastGithub.Configuration/LocalMachine.cs index eb63abf..bb8c842 100644 --- a/FastGithub.Configuration/LocalMachine.cs +++ b/FastGithub.Configuration/LocalMachine.cs @@ -85,7 +85,7 @@ namespace FastGithub.Configuration /// /// 最小值 /// - public static int GetAvailablePort(AddressFamily addressFamily, int min = 1024) + public static int GetAvailablePort(AddressFamily addressFamily, int min = 1025) { var hashSet = new HashSet(); var tcpListeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners(); diff --git a/FastGithub.Dns/DnsDnsPoisoningHostedService.cs b/FastGithub.Dns/DnsDnsPoisoningHostedService.cs new file mode 100644 index 0000000..cbb6bda --- /dev/null +++ b/FastGithub.Dns/DnsDnsPoisoningHostedService.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace FastGithub.Dns +{ + /// + /// dns投毒后台服务 + /// + [SupportedOSPlatform("windows")] + sealed class DnsDnsPoisoningHostedService : BackgroundService + { + private readonly DnsPoisoningServer dnsPoisoningServer; + private readonly IEnumerable conflictValidators; + + /// + /// dns后台服务 + /// + /// + /// + public DnsDnsPoisoningHostedService( + DnsPoisoningServer dnsPoisoningServer, + IEnumerable conflictValidators) + { + this.dnsPoisoningServer = dnsPoisoningServer; + this.conflictValidators = conflictValidators; + } + + /// + /// dns后台 + /// + /// + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Yield(); + + if (OperatingSystem.IsWindows()) + { + foreach (var item in this.conflictValidators) + { + await item.ValidateAsync(); + } + this.dnsPoisoningServer.DnsPoisoning(stoppingToken); + } + } + } +} diff --git a/FastGithub.Dns/DnsOverHttpsApplicationBuilderExtensions.cs b/FastGithub.Dns/DnsOverHttpsApplicationBuilderExtensions.cs deleted file mode 100644 index 9255fd8..0000000 --- a/FastGithub.Dns/DnsOverHttpsApplicationBuilderExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using FastGithub.Dns; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace FastGithub -{ - /// - /// DoH的中间件扩展 - /// - public static class DnsOverHttpsApplicationBuilderExtensions - { - /// - /// 使用DoH的中间件 - /// - /// - /// - public static IApplicationBuilder UseDnsOverHttps(this IApplicationBuilder app) - { - var middleware = app.ApplicationServices.GetRequiredService(); - return app.Use(next => context => middleware.InvokeAsync(context, next)); - } - } -} diff --git a/FastGithub.Dns/DnsOverHttpsMiddleware.cs b/FastGithub.Dns/DnsOverHttpsMiddleware.cs deleted file mode 100644 index de63218..0000000 --- a/FastGithub.Dns/DnsOverHttpsMiddleware.cs +++ /dev/null @@ -1,129 +0,0 @@ -using DNS.Protocol; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace FastGithub.Dns -{ - /// - /// DoH中间件 - /// - sealed class DnsOverHttpsMiddleware - { - private static readonly PathString dnsQueryPath = "/dns-query"; - private const string MEDIA_TYPE = "application/dns-message"; - private readonly RequestResolver requestResolver; - private readonly ILogger logger; - - /// - /// DoH中间件 - /// - /// - /// - public DnsOverHttpsMiddleware( - RequestResolver requestResolver, - ILogger logger) - { - this.requestResolver = requestResolver; - this.logger = logger; - } - - /// - /// 执行请求 - /// - /// - /// - /// - public async Task InvokeAsync(HttpContext context, RequestDelegate next) - { - Request? request; - try - { - request = await ParseDnsRequestAsync(context.Request); - } - catch (Exception) - { - context.Response.StatusCode = StatusCodes.Status400BadRequest; - return; - } - - if (request == null) - { - await next(context); - return; - } - - var response = await this.ResolveAsync(context, request); - context.Response.ContentType = MEDIA_TYPE; - await context.Response.BodyWriter.WriteAsync(response.ToArray()); - } - - /// - /// 解析dns域名 - /// - /// - /// - /// - private async Task ResolveAsync(HttpContext context, Request request) - { - try - { - var remoteIPAddress = context.Connection.RemoteIpAddress ?? IPAddress.Loopback; - var remoteEndPoint = new IPEndPoint(remoteIPAddress, context.Connection.RemotePort); - var remoteEndPointRequest = new RemoteEndPointRequest(request, remoteEndPoint); - return await this.requestResolver.Resolve(remoteEndPointRequest); - } - catch (Exception ex) - { - this.logger.LogWarning($"处理DNS异常:{ex.Message}"); - return Response.FromRequest(request); - } - } - - /// - /// 解析dns请求 - /// - /// - /// - private static async Task ParseDnsRequestAsync(HttpRequest request) - { - if (request.Path != dnsQueryPath || - request.Headers.TryGetValue("accept", out var accept) == false || - accept.Contains(MEDIA_TYPE) == false) - { - return default; - } - - if (request.Method == HttpMethods.Get) - { - if (request.Query.TryGetValue("dns", out var dns) == false) - { - return default; - } - - var dnsRequest = dns.ToString().Replace('-', '+').Replace('_', '/'); - int mod = dnsRequest.Length % 4; - if (mod > 0) - { - dnsRequest = dnsRequest.PadRight(dnsRequest.Length - mod + 4, '='); - } - - var message = Convert.FromBase64String(dnsRequest); - return Request.FromArray(message); - } - - if (request.Method == HttpMethods.Post && request.ContentType == MEDIA_TYPE) - { - using var message = new MemoryStream(); - await request.Body.CopyToAsync(message); - return Request.FromArray(message.ToArray()); - } - - return default; - } - } -} diff --git a/FastGithub.Dns/DnsOverUdpHostedService.cs b/FastGithub.Dns/DnsOverUdpHostedService.cs deleted file mode 100644 index 4328e3a..0000000 --- a/FastGithub.Dns/DnsOverUdpHostedService.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace FastGithub.Dns -{ - /// - /// dns后台服务 - /// - sealed class DnsOverUdpHostedService : BackgroundService - { - private readonly DnsOverUdpServer dnsOverUdpServer; - private readonly DnsPoisoningServer dnsPoisoningServer; - private readonly IEnumerable conflictValidators; - private readonly ILogger logger; - - /// - /// dns后台服务 - /// - /// - /// - /// - /// - public DnsOverUdpHostedService( - DnsOverUdpServer dnsOverUdpServer, - DnsPoisoningServer dnsPoisoningServer, - IEnumerable conflictValidators, - ILogger logger) - { - this.dnsOverUdpServer = dnsOverUdpServer; - this.dnsPoisoningServer = dnsPoisoningServer; - this.conflictValidators = conflictValidators; - this.logger = logger; - } - - /// - /// 启动dns - /// - /// - /// - public override async Task StartAsync(CancellationToken cancellationToken) - { - if (OperatingSystem.IsWindows() == false) - { - try - { - const int DNS_PORT = 53; - this.dnsOverUdpServer.Listen(IPAddress.Any, DNS_PORT); - this.logger.LogInformation($"已监听udp端口{DNS_PORT},DNS服务启动完成"); - } - catch (Exception ex) - { - var builder = new StringBuilder().AppendLine($"DNS服务启动失败({ex.Message}),你可以选择如下的一种操作:"); - builder.AppendLine($"1. 关闭占用udp53端口的进程然后重新打开本程序"); - builder.AppendLine($"2. 配置系统或浏览器使用DNS over HTTPS:https://127.0.0.1/dns-query"); - builder.AppendLine($"3. 向系统hosts文件添加github相关域名的ip为127.0.0.1"); - builder.Append($"4. 在局域网其它设备上运行本程序,然后将本机DNS设置为局域网设备的IP"); - this.logger.LogError(builder.ToString()); - } - } - - foreach (var item in this.conflictValidators) - { - await item.ValidateAsync(); - } - - await base.StartAsync(cancellationToken); - } - - /// - /// dns后台 - /// - /// - /// - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - if (OperatingSystem.IsWindows()) - { - await Task.Yield(); - this.dnsPoisoningServer.DnsPoisoning(stoppingToken); - } - else - { - await this.dnsOverUdpServer.HandleAsync(stoppingToken); - } - } - - /// - /// 停止dns服务 - /// - /// - /// - public override Task StopAsync(CancellationToken cancellationToken) - { - if (OperatingSystem.IsWindows() == false) - { - this.dnsOverUdpServer.Dispose(); - } - return base.StopAsync(cancellationToken); - } - } -} diff --git a/FastGithub.Dns/DnsOverUdpServer.cs b/FastGithub.Dns/DnsOverUdpServer.cs deleted file mode 100644 index 517d1d4..0000000 --- a/FastGithub.Dns/DnsOverUdpServer.cs +++ /dev/null @@ -1,141 +0,0 @@ -using DNS.Protocol; -using FastGithub.Configuration; -using Microsoft.Extensions.Logging; -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -namespace FastGithub.Dns -{ - /// - /// dns服务器 - /// - sealed class DnsOverUdpServer : IDisposable - { - private readonly RequestResolver requestResolver; - private readonly ILogger logger; - private readonly Socket socket = new(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - private readonly byte[] buffer = new byte[ushort.MaxValue]; - - private bool listened = false; - - /// - /// dns服务器 - /// - /// - /// - public DnsOverUdpServer( - RequestResolver requestResolver, - ILogger logger) - { - this.requestResolver = requestResolver; - this.logger = logger; - } - - /// - /// 监听IP地址和端口 - /// - /// - /// - /// - /// - public void Listen(IPAddress address, int port) - { - if (LocalMachine.CanListenUdp(port) == false) - { - throw new FastGithubException($"udp端口{port}已经被其它进程占用"); - } - - if (OperatingSystem.IsWindows()) - { - const int SIO_UDP_CONNRESET = unchecked((int)0x9800000C); - this.socket.IOControl(SIO_UDP_CONNRESET, new byte[4], new byte[4]); - } - - this.socket.Bind(new IPEndPoint(address, port)); - this.listened = true; - - try - { - SystemDnsUtil.SetAsPrimitiveDns(); - } - catch (Exception ex) - { - this.logger.LogWarning(ex.Message); - } - } - - /// - /// 监听和处理dns请求 - /// - /// - /// - public async Task HandleAsync(CancellationToken cancellationToken) - { - if (this.listened == false) - { - return; - } - - var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - while (cancellationToken.IsCancellationRequested == false) - { - try - { - var result = await this.socket.ReceiveFromAsync(this.buffer, SocketFlags.None, remoteEndPoint); - var datas = new byte[result.ReceivedBytes]; - this.buffer.AsSpan(0, datas.Length).CopyTo(datas); - this.HandleRequestAsync(datas, result.RemoteEndPoint, cancellationToken); - } - catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) - { - break; - } - } - } - - /// - /// 处理dns请求 - /// - /// - /// - /// - private async void HandleRequestAsync(byte[] datas, EndPoint remoteEndPoint, CancellationToken cancellationToken) - { - try - { - var request = Request.FromArray(datas); - var remoteEndPointRequest = new RemoteEndPointRequest(request, remoteEndPoint); - var response = await this.requestResolver.Resolve(remoteEndPointRequest, cancellationToken); - await this.socket.SendToAsync(response.ToArray(), SocketFlags.None, remoteEndPoint); - } - catch (Exception ex) - { - this.logger.LogWarning($"处理DNS异常:{ex.Message}"); - } - } - - /// - /// 释放资源 - /// - public void Dispose() - { - this.socket.Dispose(); - if (this.listened == false) - { - return; - } - - try - { - SystemDnsUtil.RemoveFromPrimitiveDns(); - } - catch (Exception ex) - { - this.logger.LogWarning(ex.Message); - } - } - } -} diff --git a/FastGithub.Dns/DnsPoisoningServer.cs b/FastGithub.Dns/DnsPoisoningServer.cs index c1403ce..ee1414f 100644 --- a/FastGithub.Dns/DnsPoisoningServer.cs +++ b/FastGithub.Dns/DnsPoisoningServer.cs @@ -16,19 +16,19 @@ namespace FastGithub.Dns /// /// dns投毒服务 /// + [SupportedOSPlatform("windows")] sealed class DnsPoisoningServer { const string DNS_FILTER = "udp.DstPort == 53"; private readonly FastGithubConfig fastGithubConfig; private readonly ILogger logger; - private readonly TimeSpan ttl = TimeSpan.FromSeconds(10d); + private readonly TimeSpan ttl = TimeSpan.FromSeconds(10d); /// /// 刷新DNS缓存 - /// - [SupportedOSPlatform("windows")] + /// [DllImport("dnsapi.dll", EntryPoint = "DnsFlushResolverCache", SetLastError = true)] - private static extern void DnsFlushResolverCache(); + private static extern void DnsFlushResolverCache(); /// /// dns投毒后台服务 @@ -47,7 +47,6 @@ namespace FastGithub.Dns /// DNS投毒 /// /// - [SupportedOSPlatform("windows")] public void DnsPoisoning(CancellationToken cancellationToken) { var handle = WinDivert.WinDivertOpen(DNS_FILTER, WinDivertLayer.Network, 0, WinDivertOpenFlags.None); diff --git a/FastGithub.Dns/RemoteEndPointRequest.cs b/FastGithub.Dns/RemoteEndPointRequest.cs deleted file mode 100644 index a997d7e..0000000 --- a/FastGithub.Dns/RemoteEndPointRequest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using DNS.Protocol; -using FastGithub.Configuration; -using System.Net; - -namespace FastGithub.Dns -{ - /// - /// 带远程终节点的请求 - /// - sealed class RemoteEndPointRequest : Request - { - /// - /// 获取程终节点 - /// - public EndPoint RemoteEndPoint { get; } - - /// - /// 远程请求 - /// - /// - /// - public RemoteEndPointRequest(Request request, EndPoint remoteEndPoint) - : base(request) - { - this.RemoteEndPoint = remoteEndPoint; - } - - /// - /// 获取对应的本机地址 - /// - /// - public IPAddress? GetLocalIPAddress() - { - return LocalMachine.GetLocalIPAddress(this.RemoteEndPoint); - } - } -} diff --git a/FastGithub.Dns/RequestResolver.cs b/FastGithub.Dns/RequestResolver.cs deleted file mode 100644 index e876530..0000000 --- a/FastGithub.Dns/RequestResolver.cs +++ /dev/null @@ -1,80 +0,0 @@ -using DNS.Client.RequestResolver; -using DNS.Protocol; -using DNS.Protocol.ResourceRecords; -using FastGithub.Configuration; -using System; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace FastGithub.Dns -{ - /// - /// dns解析者 - /// - sealed class RequestResolver : IRequestResolver - { - private readonly TimeSpan ttl = TimeSpan.FromMinutes(1d); - private readonly FastGithubConfig fastGithubConfig; - - /// - /// dns解析者 - /// - /// - public RequestResolver(FastGithubConfig fastGithubConfig) - { - this.fastGithubConfig = fastGithubConfig; - } - - /// - /// 解析域名 - /// - /// - /// - /// - public async Task Resolve(IRequest request, CancellationToken cancellationToken = default) - { - var response = Response.FromRequest(request); - if (request is not RemoteEndPointRequest remoteEndPointRequest) - { - return response; - } - - var question = request.Questions.FirstOrDefault(); - if (question == null || question.Type != RecordType.A) - { - return response; - } - - // 解析匹配的域名指向本机ip - var domain = question.Name; - if (this.fastGithubConfig.IsMatch(domain.ToString()) == true) - { - var localAddress = remoteEndPointRequest.GetLocalIPAddress() ?? IPAddress.Loopback; - var record = new IPAddressResourceRecord(domain, localAddress, this.ttl); - response.AnswerRecords.Add(record); - return response; - } - - // 使用回退dns解析域名 - foreach (var dns in this.fastGithubConfig.FallbackDns) - { - try - { - var fallbackResolver = new UdpRequestResolver(dns); - var fallbackResponse = await fallbackResolver.Resolve(request, cancellationToken); - if (fallbackResponse != null && fallbackResponse.AnswerRecords.Count > 0) - { - return fallbackResponse; - } - } - catch (Exception) - { - } - } - - return response; - } - } -} diff --git a/FastGithub.Dns/ServiceCollectionExtensions.cs b/FastGithub.Dns/ServiceCollectionExtensions.cs index 21c27d2..8725e49 100644 --- a/FastGithub.Dns/ServiceCollectionExtensions.cs +++ b/FastGithub.Dns/ServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ using FastGithub.Dns; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using System.Runtime.Versioning; namespace FastGithub { @@ -10,19 +11,17 @@ namespace FastGithub public static class ServiceCollectionExtensions { /// - /// 注册dns服务 + /// 注册dns投毒服务 /// /// /// - public static IServiceCollection AddDnsServer(this IServiceCollection services) - { - services.TryAddSingleton(); - services.TryAddSingleton(); + [SupportedOSPlatform("windows")] + public static IServiceCollection AddDnsPoisoning(this IServiceCollection services) + { services.TryAddSingleton(); - services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); - return services.AddHostedService(); + return services.AddHostedService(); } } } diff --git a/FastGithub.Dns/SystemDnsUtil.cs b/FastGithub.Dns/SystemDnsUtil.cs deleted file mode 100644 index 69a3c14..0000000 --- a/FastGithub.Dns/SystemDnsUtil.cs +++ /dev/null @@ -1,89 +0,0 @@ -using FastGithub.Configuration; -using System; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; - -namespace FastGithub.Dns -{ - /// - /// 系统域名服务工具 - /// - static class SystemDnsUtil - { - /// - /// 设置为主dns - /// - /// - public static void SetAsPrimitiveDns() - { - var @interface = GetOutboundNetworkInterface(); - if (@interface == null) - { - throw new FastGithubException($"找不到匹配的网络适配器来设置主DNS"); - } - - var dnsAddresses = @interface.GetIPProperties().DnsAddresses; - var firstRecord = dnsAddresses.FirstOrDefault(); - if (firstRecord == null || LocalMachine.ContainsIPAddress(firstRecord) == false) - { - var primitive = IPAddress.Loopback; - if (OperatingSystem.IsLinux()) - { - throw new FastGithubException($"不支持自动设置本机DNS,请手工添加{primitive}做为/etc/resolv.conf的第一条记录"); - } - else if (OperatingSystem.IsMacOS()) - { - throw new FastGithubException($"不支持自动设置本机DNS,请手工添加{primitive}做为连接网络的DNS的第一条记录"); - } - } - } - - /// - /// 从主dns移除 - /// - /// - public static void RemoveFromPrimitiveDns() - { - var @interface = GetOutboundNetworkInterface(); - if (@interface == null) - { - throw new FastGithubException($"找不到匹配的网络适配器来移除主DNS"); - } - - var dnsAddresses = @interface.GetIPProperties().DnsAddresses; - var firstRecord = dnsAddresses.FirstOrDefault(); - if (firstRecord != null && LocalMachine.ContainsIPAddress(firstRecord)) - { - if (OperatingSystem.IsLinux()) - { - throw new FastGithubException($"不支持自动移除本机主DNS,请手工移除/etc/resolv.conf的第一条记录"); - } - else if (OperatingSystem.IsMacOS()) - { - throw new FastGithubException($"不支持自动移除本机主DNS,请手工移除连接网络的DNS的第一条记录"); - } - } - } - - - /// - /// 查找出口的网络适器 - /// - /// - private static NetworkInterface? GetOutboundNetworkInterface() - { - var remoteEndPoint = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 53); - var address = LocalMachine.GetLocalIPAddress(remoteEndPoint); - if (address == null) - { - return default; - } - - return NetworkInterface - .GetAllNetworkInterfaces() - .Where(item => item.GetIPProperties().UnicastAddresses.Any(a => a.Address.Equals(address))) - .FirstOrDefault(); - } - } -} \ No newline at end of file diff --git a/FastGithub.ReverseProxy/ApplicationBuilderExtensions.cs b/FastGithub.ReverseProxy/ApplicationBuilderExtensions.cs index 854b684..e57b668 100644 --- a/FastGithub.ReverseProxy/ApplicationBuilderExtensions.cs +++ b/FastGithub.ReverseProxy/ApplicationBuilderExtensions.cs @@ -9,6 +9,17 @@ namespace FastGithub /// public static class ApplicationBuilderExtensions { + /// + /// 使用http代理中间件 + /// + /// + /// + public static IApplicationBuilder UseHttpProxy(this IApplicationBuilder app) + { + var middleware = app.ApplicationServices.GetRequiredService(); + return app.Use(next => context => middleware.InvokeAsync(context, next)); + } + /// /// 使用请求日志中间件 /// @@ -25,9 +36,9 @@ namespace FastGithub /// /// /// - public static IApplicationBuilder UseReverseProxy(this IApplicationBuilder app) + public static IApplicationBuilder UseHttpReverseProxy(this IApplicationBuilder app) { - var middleware = app.ApplicationServices.GetRequiredService(); + var middleware = app.ApplicationServices.GetRequiredService(); return app.Use(next => context => middleware.InvokeAsync(context, next)); } } diff --git a/FastGithub.ReverseProxy/HttpProxyMiddleware.cs b/FastGithub.ReverseProxy/HttpProxyMiddleware.cs new file mode 100644 index 0000000..9915b54 --- /dev/null +++ b/FastGithub.ReverseProxy/HttpProxyMiddleware.cs @@ -0,0 +1,112 @@ +using FastGithub.Configuration; +using FastGithub.DomainResolve; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using System.IO.Pipelines; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading.Tasks; +using Yarp.ReverseProxy.Forwarder; + +namespace FastGithub.ReverseProxy +{ + /// + /// http代理中间件 + /// + sealed class HttpProxyMiddleware + { + private readonly FastGithubConfig fastGithubConfig; + private readonly IDomainResolver domainResolver; + private readonly IHttpForwarder httpForwarder; + private readonly PortService portService; + private readonly SocketsHttpHandler socketsHttpHandler = new() { UseCookies = false, UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None }; + + /// + /// http代理中间件 + /// + /// + /// + /// + /// + public HttpProxyMiddleware( + FastGithubConfig fastGithubConfig, + IDomainResolver domainResolver, + IHttpForwarder httpForwarder, + PortService portService) + { + this.fastGithubConfig = fastGithubConfig; + this.domainResolver = domainResolver; + this.httpForwarder = httpForwarder; + this.portService = portService; + } + + /// + /// 处理请求 + /// + /// + /// + /// + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + if (context.Request.Method != HttpMethods.Connect) + { + var httpClient = new HttpMessageInvoker(this.socketsHttpHandler, false); + var destinationPrefix = $"{context.Request.Scheme}://{context.Request.Host}"; + await this.httpForwarder.SendAsync(context, destinationPrefix, httpClient); + } + else + { + var endpoint = await this.GetTargetEndPointAsync(context.Request); + using var targetSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); + await targetSocket.ConnectAsync(endpoint); + + context.Response.StatusCode = StatusCodes.Status200OK; + context.Features.Get().ReasonPhrase = "Connection Established"; + await context.Response.CompleteAsync(); + + var transport = context.Features.Get()?.Transport; + if (transport != null) + { + var targetStream = new NetworkStream(targetSocket, ownsSocket: false); + var task1 = targetStream.CopyToAsync(transport.Output); + var task2 = transport.Input.CopyToAsync(targetStream); + await Task.WhenAny(task1, task2); + } + } + } + + + /// + /// 获取目标终节点 + /// + /// + /// + private async Task GetTargetEndPointAsync(HttpRequest request) + { + var domain = request.Host.Host; + var port = request.Host.Port ?? 443; + + if (IPAddress.TryParse(domain, out var address) == true) + { + return new IPEndPoint(address, port); + } + + if (this.fastGithubConfig.TryGetDomainConfig(domain, out _) == false) + { + return new DnsEndPoint(domain, port); + } + + // https,走反向代理中间人 + if (port == 443) + { + return new IPEndPoint(IPAddress.Loopback, this.portService.HttpsReverseProxyPort); + } + + // dns优选 + address = await this.domainResolver.ResolveAsync(new DnsEndPoint(domain, port)); + return new IPEndPoint(address, port); + } + } +} \ No newline at end of file diff --git a/FastGithub.ReverseProxy/ReverseProxyMiddleware.cs b/FastGithub.ReverseProxy/HttpReverseProxyMiddleware.cs similarity index 94% rename from FastGithub.ReverseProxy/ReverseProxyMiddleware.cs rename to FastGithub.ReverseProxy/HttpReverseProxyMiddleware.cs index f8b9766..5041045 100644 --- a/FastGithub.ReverseProxy/ReverseProxyMiddleware.cs +++ b/FastGithub.ReverseProxy/HttpReverseProxyMiddleware.cs @@ -11,18 +11,18 @@ namespace FastGithub.ReverseProxy /// /// 反向代理中间件 /// - sealed class ReverseProxyMiddleware + sealed class HttpReverseProxyMiddleware { private readonly IHttpForwarder httpForwarder; private readonly IHttpClientFactory httpClientFactory; private readonly FastGithubConfig fastGithubConfig; - private readonly ILogger logger; + private readonly ILogger logger; - public ReverseProxyMiddleware( + public HttpReverseProxyMiddleware( IHttpForwarder httpForwarder, IHttpClientFactory httpClientFactory, FastGithubConfig fastGithubConfig, - ILogger logger) + ILogger logger) { this.httpForwarder = httpForwarder; this.httpClientFactory = httpClientFactory; diff --git a/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs b/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs index a6c8c43..71b7018 100644 --- a/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs +++ b/FastGithub.ReverseProxy/KestrelServerOptionsExtensions.cs @@ -25,24 +25,40 @@ namespace FastGithub } /// - /// 监听ssh + /// 监听http代理 /// /// - public static void ListenSsh(this KestrelServerOptions kestrel) + public static void ListenHttpProxy(this KestrelServerOptions kestrel) + { + var httpPort = kestrel.ApplicationServices.GetRequiredService().HttpProxyPort; + if (LocalMachine.CanListenTcp(httpPort) == false) + { + throw new FastGithubException("tcp端口{httpsPort}已经被其它进程占用,请在配置文件更换一个端口"); + } + + kestrel.Listen(IPAddress.Any, httpPort); + kestrel.GetLogger().LogInformation($"已监听tcp端口{httpPort},http代理启动完成"); + } + + /// + /// 监听ssh反向代理 + /// + /// + public static void ListenSshReverseProxy(this KestrelServerOptions kestrel) { const int SSH_PORT = 22; if (LocalMachine.CanListenTcp(SSH_PORT) == true) { - kestrel.Listen(IPAddress.Any, SSH_PORT, listen => listen.UseConnectionHandler()); + kestrel.Listen(IPAddress.Any, SSH_PORT, listen => listen.UseConnectionHandler()); kestrel.GetLogger().LogInformation($"已监听tcp端口{SSH_PORT},github的ssh代理启动完成"); } } /// - /// 监听http + /// 监听http反向代理 /// /// - public static void ListenHttp(this KestrelServerOptions kestrel) + public static void ListenHttpReverseProxy(this KestrelServerOptions kestrel) { const int HTTP_PORT = 80; if (LocalMachine.CanListenTcp(HTTP_PORT) == true) @@ -53,34 +69,34 @@ namespace FastGithub } /// - /// 监听https + /// 监听https反向代理 /// /// /// - public static void ListenHttps(this KestrelServerOptions kestrel) + public static void ListenHttpsReverseProxy(this KestrelServerOptions kestrel) { - const int HTTPS_PORT = 443; + var httpsPort = kestrel.ApplicationServices.GetRequiredService().HttpsReverseProxyPort; if (OperatingSystem.IsWindows()) { - TcpTable.KillPortOwner(HTTPS_PORT); + TcpTable.KillPortOwner(httpsPort); } - if (LocalMachine.CanListenTcp(HTTPS_PORT) == false) + if (LocalMachine.CanListenTcp(httpsPort) == false) { - throw new FastGithubException($"tcp端口{HTTPS_PORT}已经被其它进程占用"); + throw new FastGithubException($"tcp端口{httpsPort}已经被其它进程占用"); } var certService = kestrel.ApplicationServices.GetRequiredService(); certService.CreateCaCertIfNotExists(); certService.InstallAndTrustCaCert(); - kestrel.Listen(IPAddress.Any, HTTPS_PORT, + kestrel.Listen(IPAddress.Any, httpsPort, listen => listen.UseHttps(https => https.ServerCertificateSelector = (ctx, domain) => certService.GetOrCreateServerCert(domain))); var logger = kestrel.GetLogger(); - logger.LogInformation($"已监听tcp端口{HTTPS_PORT},https反向代理启动完成"); + logger.LogInformation($"已监听tcp端口{httpsPort},https反向代理启动完成"); } diff --git a/FastGithub.ReverseProxy/PortService.cs b/FastGithub.ReverseProxy/PortService.cs new file mode 100644 index 0000000..31074f4 --- /dev/null +++ b/FastGithub.ReverseProxy/PortService.cs @@ -0,0 +1,48 @@ +using FastGithub.Configuration; +using Microsoft.Extensions.Options; +using System; +using System.Net.Sockets; + +namespace FastGithub.ReverseProxy +{ + /// + /// 端口管理服务 + /// + public class PortService + { + private int httpsReverseProxyPort = -1; + + /// + /// http代理端口 + /// + public int HttpProxyPort { get; } + + /// + /// 获取https反向代理端口 + /// + public int HttpsReverseProxyPort + { + get + { + if (OperatingSystem.IsWindows()) + { + return 443; + } + if (this.httpsReverseProxyPort < 0) + { + this.httpsReverseProxyPort = LocalMachine.GetAvailablePort(AddressFamily.InterNetwork); + } + return this.httpsReverseProxyPort; + } + } + + /// + /// 端口管理服务 + /// + /// + public PortService(IOptions options) + { + this.HttpProxyPort = options.Value.HttpProxyPort; + } + } +} diff --git a/FastGithub.ReverseProxy/ServiceCollectionExtensions.cs b/FastGithub.ReverseProxy/ServiceCollectionExtensions.cs index bd9598f..f265b0e 100644 --- a/FastGithub.ReverseProxy/ServiceCollectionExtensions.cs +++ b/FastGithub.ReverseProxy/ServiceCollectionExtensions.cs @@ -19,8 +19,10 @@ namespace FastGithub .AddMemoryCache() .AddHttpForwarder() .AddSingleton() - .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); } } } diff --git a/FastGithub.ReverseProxy/GithubSshProxyHandler.cs b/FastGithub.ReverseProxy/SshReverseProxyHandler.cs similarity index 91% rename from FastGithub.ReverseProxy/GithubSshProxyHandler.cs rename to FastGithub.ReverseProxy/SshReverseProxyHandler.cs index f7b7883..d08d764 100644 --- a/FastGithub.ReverseProxy/GithubSshProxyHandler.cs +++ b/FastGithub.ReverseProxy/SshReverseProxyHandler.cs @@ -10,7 +10,7 @@ namespace FastGithub.ReverseProxy /// /// github的ssh代理处理者 /// - sealed class GithubSshProxyHandler : ConnectionHandler + sealed class SshReverseProxyHandler : ConnectionHandler { private readonly IDomainResolver domainResolver; private readonly DnsEndPoint githubSshEndPoint = new("ssh.github.com", 443); @@ -19,7 +19,7 @@ namespace FastGithub.ReverseProxy /// github的ssh代理处理者 /// /// - public GithubSshProxyHandler(IDomainResolver domainResolver) + public SshReverseProxyHandler(IDomainResolver domainResolver) { this.domainResolver = domainResolver; } diff --git a/FastGithub/Program.cs b/FastGithub/Program.cs index f94e173..87a0fe7 100644 --- a/FastGithub/Program.cs +++ b/FastGithub/Program.cs @@ -52,9 +52,17 @@ namespace FastGithub webBuilder.UseKestrel(kestrel => { kestrel.NoLimit(); - kestrel.ListenHttps(); - kestrel.ListenHttp(); - kestrel.ListenSsh(); + kestrel.ListenHttpsReverseProxy(); + + if (OperatingSystem.IsWindows()) + { + kestrel.ListenHttpReverseProxy(); + kestrel.ListenSshReverseProxy(); + } + else + { + kestrel.ListenHttpProxy(); + } }); webBuilder.UseSerilog((hosting, logger) => { diff --git a/FastGithub/Startup.cs b/FastGithub/Startup.cs index d4b0d83..4af2cdf 100644 --- a/FastGithub/Startup.cs +++ b/FastGithub/Startup.cs @@ -1,9 +1,9 @@ using FastGithub.Configuration; +using FastGithub.ReverseProxy; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using System.IO; +using System; using System.Threading.Tasks; namespace FastGithub @@ -34,10 +34,14 @@ namespace FastGithub services.AddConfiguration(); services.AddDomainResolve(); - services.AddDnsServer(); services.AddHttpClient(); services.AddReverseProxy(); services.AddHostedService(); + + if (OperatingSystem.IsWindows()) + { + services.AddDnsPoisoning(); + } } /// @@ -46,26 +50,31 @@ namespace FastGithub /// public void Configure(IApplicationBuilder app) { - app.UseRequestLogging(); - app.UseDnsOverHttps(); - app.UseReverseProxy(); - - app.UseRouting(); - app.UseEndpoints(endpoint => + if (OperatingSystem.IsWindows()) { - endpoint.Map("/", async context => - { - var certFile = $"CACert/{nameof(FastGithub)}.cer"; - context.Response.ContentType = "application/x-x509-ca-cert"; - context.Response.Headers.Add("Content-Disposition", $"attachment;filename={Path.GetFileName(certFile)}"); - await context.Response.SendFileAsync(certFile); - }); - endpoint.MapFallback(context => + app.UseRequestLogging(); + app.UseHttpReverseProxy(); + app.UseRouting(); + app.UseEndpoints(endpoint => endpoint.MapFallback(context => { context.Response.Redirect("https://github.com/dotnetcore/FastGithub"); return Task.CompletedTask; + })); + } + else + { + var portService = app.ApplicationServices.GetRequiredService(); + app.MapWhen(context => context.Connection.LocalPort == portService.HttpProxyPort, appBuilder => + { + appBuilder.UseHttpProxy(); }); - }); + + app.MapWhen(context => context.Connection.LocalPort != portService.HttpProxyPort, appBuilder => + { + appBuilder.UseRequestLogging(); + appBuilder.UseHttpReverseProxy(); + }); + } } } } diff --git a/FastGithub/appsettings.json b/FastGithub/appsettings.json index 15aab34..7efe097 100644 --- a/FastGithub/appsettings.json +++ b/FastGithub/appsettings.json @@ -1,6 +1,7 @@ { // 新增的子配置文件appsettings.*.json,重启应用程序才生效 "FastGithub": { + "HttpProxyPort": 2222, // http代理端口,非windows才使用 "FallbackDns": [ // 用于解析不在DomainConfigs的域名 { "IPAddress": "114.114.114.114",