完善反向代理功能

This commit is contained in:
xljiulang 2021-07-13 21:27:53 +08:00
parent ae6b0d7260
commit 9a0c2aab5f
17 changed files with 365 additions and 104 deletions

View File

@ -23,5 +23,10 @@ namespace FastGithub.Dns
/// 是否设置本机使用此dns
/// </summary>
public bool SetToLocalMachine { get; set; } = true;
/// <summary>
/// 是否启用反向代理
/// </summary>
public bool UseReverseProxy { get; set; } = true;
}
}

View File

@ -6,7 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@ -44,7 +44,7 @@ namespace FastGithub.Dns
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task<IResponse> Resolve(IRequest request, CancellationToken cancellationToken = default)
public async Task<IResponse> Resolve(IRequest request, CancellationToken cancellationToken = default)
{
var response = Response.FromRequest(request);
var question = request.Questions.FirstOrDefault();
@ -56,15 +56,31 @@ namespace FastGithub.Dns
if (address != null)
{
address = IPAddress.Loopback;
var ttl = this.options.CurrentValue.GithubTTL;
if (this.options.CurrentValue.UseReverseProxy == false)
{
var record = new IPAddressResourceRecord(question.Name, address, ttl);
response.AnswerRecords.Add(record);
this.logger.LogInformation(record.ToString());
}
else
{
var hostName = System.Net.Dns.GetHostName();
var addresses = await System.Net.Dns.GetHostAddressesAsync(hostName);
foreach (var item in addresses)
{
if (item.AddressFamily == AddressFamily.InterNetwork)
{
var record = new IPAddressResourceRecord(question.Name, item, ttl);
response.AnswerRecords.Add(record);
this.logger.LogInformation(record.ToString());
}
}
}
}
}
return Task.FromResult<IResponse>(response);
return response;
}
}
}

View File

@ -38,7 +38,7 @@ namespace FastGithub.ReverseProxy
/// <param name="caPublicCerPath"></param>
/// <param name="caPrivateKeyPath"></param>
/// <returns></returns>
public static X509Certificate2 Generate(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath)
public static X509Certificate2 Generate(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath, string? password = default)
{
if (File.Exists(caPublicCerPath) == false)
{
@ -61,7 +61,7 @@ namespace FastGithub.ReverseProxy
var keys = GenerateRsaKeyPair(keySizeBits);
var cert = GenerateCertificate(domains, keys.Public, validFrom, validTo, caSubjectName, caCert.GetPublicKey(), caPrivateKey, null);
return GeneratePfx(cert, keys.Private, password: null);
return GeneratePfx(cert, keys.Private, password);
}
/// <summary>

View File

@ -1,5 +1,4 @@
using FastGithub.Scanner;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading;
@ -19,9 +18,9 @@ namespace FastGithub.ReverseProxy
/// Github的dns解析的httpHandler
/// </summary>
/// <param name="scanResults"></param>
/// <param name="handler"></param>
public GithubDnsHttpHandler(IGithubScanResults scanResults, HttpMessageHandler handler)
: base(handler)
/// <param name="innerHandler"></param>
public GithubDnsHttpHandler(IGithubScanResults scanResults, HttpMessageHandler innerHandler)
: base(innerHandler)
{
this.scanResults = scanResults;
}

View File

@ -0,0 +1,58 @@
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// 表示自主管理生命周期的的HttpMessageHandler
/// </summary>
[DebuggerDisplay("LifeTime = {lifeTime}")]
sealed class LifetimeHttpHandler : DelegatingHandler
{
/// <summary>
/// 生命周期
/// </summary>
private readonly TimeSpan lifeTime;
/// <summary>
/// Token取消源
/// </summary>
private readonly CancellationTokenSource tokenSource = new CancellationTokenSource();
/// <summary>
/// 具有生命周期的HttpHandler
/// </summary>
/// <param name="handler">HttpHandler</param>
/// <param name="lifeTime">拦截器的生命周期</param>
/// <param name="deactivateAction">失效回调</param>
/// <exception cref="ArgumentNullException"></exception>
public LifetimeHttpHandler(HttpMessageHandler handler, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
: base(handler)
{
if (deactivateAction == null)
{
throw new ArgumentNullException(nameof(deactivateAction));
}
this.lifeTime = lifeTime;
this.tokenSource.Token.Register(() =>
{
this.tokenSource.Dispose();
deactivateAction.Invoke(this);
}, useSynchronizationContext: false);
this.tokenSource.CancelAfter(lifeTime);
}
/// <summary>
/// 这里不释放资源
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
}
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// 表示LifetimeHttpHandler清理器
/// </summary>
sealed class LifetimeHttpHandlerCleaner
{
/// <summary>
/// 当前监视生命周期的记录的数量
/// </summary>
private int trackingEntryCount = 0;
/// <summary>
/// 监视生命周期的记录队列
/// </summary>
private readonly ConcurrentQueue<TrackingEntry> trackingEntries = new();
/// <summary>
/// 获取或设置清理的时间间隔
/// 默认10s
/// </summary>
public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromSeconds(10d);
/// <summary>
/// 添加要清除的httpHandler
/// </summary>
/// <param name="handler">httpHandler</param>
public void Add(LifetimeHttpHandler handler)
{
var entry = new TrackingEntry(handler);
this.trackingEntries.Enqueue(entry);
// 从0变为1要启动清理作业
if (Interlocked.Increment(ref this.trackingEntryCount) == 1)
{
this.StartCleanup();
}
}
/// <summary>
/// 启动清理作业
/// </summary>
private async void StartCleanup()
{
try
{
while (true)
{
await Task
.Delay(this.CleanupInterval)
.ConfigureAwait(false);
if (this.Cleanup() == true)
{
break;
}
}
}
catch (Exception)
{
// 这是应该不可能发生的
}
}
/// <summary>
/// 清理失效的拦截器
/// 返回是否完全清理
/// </summary>
/// <returns></returns>
private bool Cleanup()
{
var cleanCount = this.trackingEntries.Count;
for (var i = 0; i < cleanCount; i++)
{
this.trackingEntries.TryDequeue(out var entry);
Debug.Assert(entry != null);
if (entry.CanDispose == false)
{
this.trackingEntries.Enqueue(entry);
continue;
}
entry.Dispose();
if (Interlocked.Decrement(ref this.trackingEntryCount) == 0)
{
return true;
}
}
return false;
}
/// <summary>
/// 表示监视生命周期的记录
/// </summary>
private class TrackingEntry : IDisposable
{
/// <summary>
/// 用于释放资源的对象
/// </summary>
private readonly IDisposable disposable;
/// <summary>
/// 监视对象的弱引用
/// </summary>
private readonly WeakReference weakReference;
/// <summary>
/// 获取是否可以释放资源
/// </summary>
/// <returns></returns>
public bool CanDispose
{
get => this.weakReference.IsAlive == false;
}
/// <summary>
/// 监视生命周期的记录
/// </summary>
/// <param name="handler">激活状态的httpHandler</param>
public TrackingEntry(LifetimeHttpHandler handler)
{
this.disposable = handler.InnerHandler!;
this.weakReference = new WeakReference(handler);
}
public void Dispose()
{
try
{
this.disposable.Dispose();
}
catch (Exception) { }
}
}
}
}

View File

@ -7,6 +7,9 @@ using System.Security.Cryptography.X509Certificates;
namespace FastGithub
{
/// <summary>
/// ListenOptions扩展
/// </summary>
public static class ListenOptionsHttpsExtensions
{
/// <summary>
@ -21,16 +24,16 @@ namespace FastGithub
return listenOptions.UseHttps(https =>
{
var certs = new ConcurrentDictionary<string, X509Certificate2>();
https.ServerCertificateSelector = (ctx, domain) =>
certs.GetOrAdd(domain, d =>
CertGenerator.Generate(
new[] { d },
2048,
DateTime.Today.AddYears(-1),
DateTime.Today.AddYears(1),
caPublicCerPath,
caPrivateKeyPath));
https.ServerCertificateSelector = (ctx, domain) => certs.GetOrAdd(domain, CreateCert);
});
X509Certificate2 CreateCert(string domain)
{
var domains = new[] { domain };
var validFrom = DateTime.Today.AddYears(-1);
var validTo = DateTime.Today.AddYears(10);
return CertGenerator.Generate(domains, 2048, validFrom, validTo, caPublicCerPath, caPrivateKeyPath);
}
}
}
}

View File

@ -1,62 +0,0 @@
using FastGithub.Scanner;
using System.IO;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// 去掉Sni的HttpClient
/// </summary>
sealed class NoneSniHttpClient : HttpMessageInvoker
{
/// <summary>
/// 去掉Sni的HttpClient
/// </summary>
/// <param name="githubScanResults"></param>
public NoneSniHttpClient(IGithubScanResults githubScanResults)
: base(CreateNoneSniHttpHandler(githubScanResults), disposeHandler: false)
{
}
/// <summary>
/// 去掉Sni的HttpHandler
/// </summary>
private static HttpMessageHandler CreateNoneSniHttpHandler(IGithubScanResults githubScanResults)
{
var httpHandler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
UseCookies = false,
UseProxy = false,
ConnectCallback = ConnectCallback
};
return new GithubDnsHttpHandler(githubScanResults, httpHandler);
}
/// <summary>
/// 连接回调
/// </summary>
/// <param name="context"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private static async ValueTask<Stream> ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
var stream = new NetworkStream(socket, ownsSocket: true);
if (context.InitialRequestMessage.Headers.Host == null)
{
return stream;
}
var sslStream = new SslStream(stream, leaveInnerStreamOpen: false, delegate { return true; });
await sslStream.AuthenticateAsClientAsync(string.Empty, null, false);
return sslStream;
}
}
}

View File

@ -0,0 +1,96 @@
using FastGithub.Scanner;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// 禁用tls sni的HttpClient工厂
/// </summary>
[Service(ServiceLifetime.Singleton)]
sealed class NoneSniHttpClientFactory
{
private readonly IGithubScanResults githubScanResults;
/// <summary>
/// 生命周期
/// </summary>
private readonly TimeSpan lifeTime = TimeSpan.FromMinutes(2d);
/// <summary>
/// 具有生命周期的httpHandler延时创建对象
/// </summary>
private Lazy<LifetimeHttpHandler> lifeTimeHttpHandlerLazy;
/// <summary>
/// HttpHandler清理器
/// </summary>
private readonly LifetimeHttpHandlerCleaner httpHandlerCleaner = new LifetimeHttpHandlerCleaner();
/// <summary>
/// 禁用tls sni的HttpClient工厂
/// </summary>
/// <param name="githubScanResults"></param>
public NoneSniHttpClientFactory(IGithubScanResults githubScanResults)
{
this.githubScanResults = githubScanResults;
this.lifeTimeHttpHandlerLazy = new Lazy<LifetimeHttpHandler>(this.CreateHttpHandler, true);
}
/// <summary>
/// 创建HttpClient
/// </summary>
/// <returns></returns>
public HttpMessageInvoker CreateHttpClient()
{
var handler = this.lifeTimeHttpHandlerLazy.Value;
return new HttpMessageInvoker(handler, disposeHandler: false);
}
/// <summary>
/// 创建具有生命周期控制的httpHandler
/// </summary>
/// <returns></returns>
private LifetimeHttpHandler CreateHttpHandler()
{
var noneSniHandler = new SocketsHttpHandler
{
Proxy = null,
UseProxy = false,
AllowAutoRedirect = false,
ConnectCallback = async (ctx, ct) =>
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(ctx.DnsEndPoint, ct);
var stream = new NetworkStream(socket, ownsSocket: true);
if (ctx.InitialRequestMessage.Headers.Host == null)
{
return stream;
}
var sslStream = new SslStream(stream, leaveInnerStreamOpen: false, delegate { return true; });
await sslStream.AuthenticateAsClientAsync(string.Empty, null, false);
return sslStream;
}
};
var dnsHandler = new GithubDnsHttpHandler(this.githubScanResults, noneSniHandler);
return new LifetimeHttpHandler(dnsHandler, this.lifeTime, this.OnHttpHandlerDeactivate);
}
/// <summary>
/// 当有httpHandler失效时
/// </summary>
/// <param name="handler">httpHandler</param>
private void OnHttpHandlerDeactivate(LifetimeHttpHandler handler)
{
// 切换激活状态的记录的实例
this.lifeTimeHttpHandlerLazy = new Lazy<LifetimeHttpHandler>(this.CreateHttpHandler, true);
this.httpHandlerCleaner.Add(handler);
}
}
}

View File

@ -18,13 +18,15 @@ namespace FastGithub
public static IApplicationBuilder UseGithubReverseProxy(this IApplicationBuilder app)
{
var httpForwarder = app.ApplicationServices.GetRequiredService<IHttpForwarder>();
var httpClient = app.ApplicationServices.GetRequiredService<NoneSniHttpClient>();
var httpClientFactory = app.ApplicationServices.GetRequiredService<NoneSniHttpClientFactory>();
app.Use(next => async context =>
{
var hostString = context.Request.Host;
var port = hostString.Port ?? 443;
var destinationPrefix = $"http://{hostString.Host}:{port}/";
var httpClient = httpClientFactory.CreateHttpClient();
await httpForwarder.SendAsync(context, destinationPrefix, httpClient);
});

View File

@ -1,5 +1,4 @@
using FastGithub.ReverseProxy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace FastGithub
@ -13,12 +12,14 @@ namespace FastGithub
/// gitub反向代理
/// </summary>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static IServiceCollection AddGithubReverseProxy(this IServiceCollection services)
public static IServiceCollection AddGithubReverseProxy(this IServiceCollection services, IConfiguration configuration)
{
services.AddHttpForwarder();
services.AddSingleton<NoneSniHttpClient>();
return services;
var assembly = typeof(ReverseProxyServiceCollectionExtensions).Assembly;
return services
.AddServiceAndOptions(assembly, configuration)
.AddHttpForwarder();
}
}
}

View File

@ -60,7 +60,7 @@ namespace FastGithub.Scanner.LookupProviders
try
{
var httpClient = this.httpClientFactory.CreateClient(nameof(FastGithub));
var httpClient = this.httpClientFactory.CreateClient(nameof(Scanner));
var meta = await GetMetaAsync(httpClient, setting.MetaUri, cancellationToken);
if (meta != null)
{

View File

@ -57,7 +57,7 @@ namespace FastGithub.Scanner.LookupProviders
return Enumerable.Empty<DomainAddress>();
}
var httpClient = this.httpClientFactory.CreateClient(nameof(FastGithub));
var httpClient = this.httpClientFactory.CreateClient(nameof(Scanner));
var result = new HashSet<DomainAddress>();
foreach (var domain in domains)
{

View File

@ -64,7 +64,7 @@ namespace FastGithub.Scanner.ScanMiddlewares
using var timeoutTokenSource = new CancellationTokenSource(timeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, context.CancellationToken);
var httpClient = this.httpClientFactory.CreateClient(nameof(FastGithub));
var httpClient = this.httpClientFactory.CreateClient(nameof(Scanner));
using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, linkedTokenSource.Token);
VerifyHttpsResponse(context.Domain, response);

View File

@ -26,8 +26,9 @@ namespace FastGithub
var defaultUserAgent = new ProductInfoHeaderValue(assembly.GetName().Name ?? nameof(FastGithub), assembly.GetName().Version?.ToString());
services
.AddHttpClient(nameof(FastGithub))
.AddHttpClient(nameof(Scanner))
.SetHandlerLifetime(TimeSpan.FromMinutes(5d))
.AddHttpMessageHandler<GithubDnsHttpHandler>()
.ConfigureHttpClient(httpClient =>
{
httpClient.Timeout = TimeSpan.FromSeconds(10d);
@ -53,8 +54,7 @@ namespace FastGithub
await sslStream.AuthenticateAsClientAsync(string.Empty, null, false);
return sslStream;
}
})
.AddHttpMessageHandler<GithubDnsHttpHandler>();
});
return services
.AddMemoryCache()

View File

@ -38,7 +38,7 @@ namespace FastGithub
{
services.AddAppUpgrade();
services.AddGithubDns(ctx.Configuration);
services.AddGithubReverseProxy();
services.AddGithubReverseProxy(ctx.Configuration);
services.AddGithubScanner(ctx.Configuration);
})
.ConfigureWebHostDefaults(web =>
@ -50,9 +50,7 @@ namespace FastGithub
web.UseKestrel(kestrel =>
{
const string caPublicCerPath = "FastGithub_CA.cer";
const string caPrivateKeyPath = "FastGithub_CA.key";
kestrel.ListenLocalhost(443, listen => listen.UseGithubHttps(caPublicCerPath, caPrivateKeyPath));
kestrel.ListenAnyIP(443, listen => listen.UseGithubHttps("FastGithub_CA.cer", "FastGithub_CA.key"));
});
});
}

View File

@ -2,7 +2,8 @@
"Dns": {
"UpStream": "114.114.114.114", // dns
"GithubTTL": "00:10:00", // github
"SetToLocalMachine": true // 使dns(windows)
"SetToLocalMachine": true, // 使dns(windows)
"UseReverseProxy": true // 使访github
},
"Lookup": { // ip
"IPAddressComProvider": {