增加http反向代理;

使用IPGlobalProperties获取全局ip;
This commit is contained in:
陈国伟 2021-07-29 13:59:22 +08:00
parent ddb6747a7a
commit 48e994182f
14 changed files with 169 additions and 130 deletions

View File

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
@ -52,17 +53,10 @@ namespace FastGithub.Configuration
/// <returns></returns> /// <returns></returns>
private static bool IsLocalMachineIPAddress(IPAddress address) private static bool IsLocalMachineIPAddress(IPAddress address)
{ {
foreach (var @interface in NetworkInterface.GetAllNetworkInterfaces()) return IPGlobalProperties
{ .GetIPGlobalProperties()
foreach (var addressInfo in @interface.GetIPProperties().UnicastAddresses) .GetUnicastAddresses()
{ .Any(item => item.Address.Equals(address));
if (addressInfo.Address.Equals(address))
{
return true;
}
}
}
return false;
} }
} }
} }

View File

@ -1,8 +1,10 @@
using DNS.Protocol; using DNS.Protocol;
using FastGithub.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Diagnostics; using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -40,9 +42,15 @@ namespace FastGithub.Dns
/// <returns></returns> /// <returns></returns>
public void Bind(IPAddress address, int port) public void Bind(IPAddress address, int port)
{ {
if (OperatingSystem.IsWindows() && UdpTable.TryGetOwnerProcessId(port, out var processId)) if (OperatingSystem.IsWindows())
{ {
Process.GetProcessById(processId).Kill(); UdpTable.KillPortOwner(port);
}
var udpListeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners();
if (udpListeners.Any(item => item.Port == port))
{
throw new FastGithubException($"udp端口{port}已经被其它进程占用");
} }
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())

View File

@ -84,15 +84,9 @@ namespace FastGithub.Dns
/// <returns></returns> /// <returns></returns>
private static IEnumerable<IPAddress> GetLocalMachineIPAddress() private static IEnumerable<IPAddress> GetLocalMachineIPAddress()
{ {
yield return IPAddress.Loopback; foreach (var item in IPGlobalProperties.GetIPGlobalProperties().GetUnicastAddresses())
yield return IPAddress.IPv6Loopback;
foreach (var @interface in NetworkInterface.GetAllNetworkInterfaces())
{ {
foreach (var addressInfo in @interface.GetIPProperties().UnicastAddresses) yield return item.Address;
{
yield return addressInfo.Address;
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -18,6 +19,34 @@ namespace FastGithub.Dns
[DllImport("iphlpapi.dll", SetLastError = true)] [DllImport("iphlpapi.dll", SetLastError = true)]
private static extern uint GetExtendedUdpTable(void* pUdpTable, ref int pdwSize, bool bOrder, AddressFamily ulAf, UDP_TABLE_CLASS tableClass, uint reserved = 0); private static extern uint GetExtendedUdpTable(void* pUdpTable, ref int pdwSize, bool bOrder, AddressFamily ulAf, UDP_TABLE_CLASS tableClass, uint reserved = 0);
/// <summary>
/// 杀死占用进程
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public static bool KillPortOwner(int port)
{
if (TryGetOwnerProcessId(port, out var pid) == false)
{
return true;
}
try
{
Process.GetProcessById(pid).Kill();
return true;
}
catch (ArgumentException)
{
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary> /// <summary>
/// 获取udp端口的占用进程id /// 获取udp端口的占用进程id
/// </summary> /// </summary>

View File

@ -22,9 +22,34 @@ namespace FastGithub
public static class KestrelServerOptionsExtensions public static class KestrelServerOptionsExtensions
{ {
/// <summary> /// <summary>
/// 域名证书缓存 /// 服务器证书缓存
/// </summary> /// </summary>
private static readonly IMemoryCache domainCertCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private static readonly IMemoryCache serverCertCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
/// <summary>
/// 监听http的反向代理
/// </summary>
/// <param name="kestrel"></param>
public static void ListenHttpReverseProxy(this KestrelServerOptions kestrel)
{
var loggerFactory = kestrel.ApplicationServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger($"{nameof(FastGithub)}.{nameof(ReverseProxy)}");
const int HTTP_PORT = 80;
if (OperatingSystem.IsWindows())
{
TcpTable.KillPortOwner(HTTP_PORT);
}
if (CanTcpListen(HTTP_PORT) == false)
{
logger.LogWarning($"无法监听tcp端口{HTTP_PORT}{nameof(FastGithub)}无法http反向代理");
}
else
{
kestrel.Listen(IPAddress.Any, HTTP_PORT);
}
}
/// <summary> /// <summary>
/// 监听https的反向代理 /// 监听https的反向代理
@ -43,10 +68,34 @@ namespace FastGithub
GeneratorCaCert(caPublicCerPath, caPrivateKeyPath); GeneratorCaCert(caPublicCerPath, caPrivateKeyPath);
InstallCaCert(caPublicCerPath, logger); InstallCaCert(caPublicCerPath, logger);
kestrel.Listen(IPAddress.Any, 443, listen => const int HTTPS_PORT = 443;
listen.UseHttps(https => if (OperatingSystem.IsWindows())
https.ServerCertificateSelector = (ctx, domain) => {
GetDomainCert(domain, caPublicCerPath, caPrivateKeyPath))); TcpTable.KillPortOwner(HTTPS_PORT);
}
if (CanTcpListen(HTTPS_PORT) == false)
{
logger.LogWarning($"无法监听tcp端口{HTTPS_PORT}{nameof(FastGithub)}无法https反向代理");
}
else
{
kestrel.Listen(IPAddress.Any, HTTPS_PORT, listen =>
listen.UseHttps(https =>
https.ServerCertificateSelector = (ctx, domain) =>
GetServerCert(domain, caPublicCerPath, caPrivateKeyPath)));
}
}
/// <summary>
/// 是否可以监听指定端口
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
private static bool CanTcpListen(int port)
{
var tcpListeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
return tcpListeners.Any(item => item.Port == port) == false;
} }
/// <summary> /// <summary>
@ -81,25 +130,24 @@ namespace FastGithub
if (OperatingSystem.IsWindows() == false) if (OperatingSystem.IsWindows() == false)
{ {
logger.LogWarning($"不支持自动安装证书{caPublicCerPath}:请手动安装证书到根证书颁发机构"); logger.LogWarning($"不支持自动安装证书{caPublicCerPath}:请手动安装证书到根证书颁发机构");
return;
} }
else
try
{ {
try var caCert = new X509Certificate2(caPublicCerPath);
using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
if (store.Certificates.Find(X509FindType.FindByThumbprint, caCert.Thumbprint, true).Count == 0)
{ {
var caCert = new X509Certificate2(caPublicCerPath); store.Add(caCert);
using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); store.Close();
store.Open(OpenFlags.ReadWrite);
if (store.Certificates.Find(X509FindType.FindByThumbprint, caCert.Thumbprint, true).Count == 0)
{
store.Add(caCert);
store.Close();
}
}
catch (Exception)
{
logger.LogWarning($"安装证书{caPublicCerPath}失败:请手动安装到“将所有的证书都放入下载存储”\\“受信任的根证书颁发机构”");
} }
} }
catch (Exception)
{
logger.LogWarning($"安装证书{caPublicCerPath}失败:请手动安装到“将所有的证书都放入下载存储”\\“受信任的根证书颁发机构”");
}
} }
/// <summary> /// <summary>
@ -109,9 +157,9 @@ namespace FastGithub
/// <param name="caPublicCerPath"></param> /// <param name="caPublicCerPath"></param>
/// <param name="caPrivateKeyPath"></param> /// <param name="caPrivateKeyPath"></param>
/// <returns></returns> /// <returns></returns>
private static X509Certificate2 GetDomainCert(string? domain, string caPublicCerPath, string caPrivateKeyPath) private static X509Certificate2 GetServerCert(string? domain, string caPublicCerPath, string caPrivateKeyPath)
{ {
return domainCertCache.GetOrCreate(domain ?? string.Empty, GetOrCreateCert); return serverCertCache.GetOrCreate(domain ?? string.Empty, GetOrCreateCert);
// 生成域名的1年证书 // 生成域名的1年证书
X509Certificate2 GetOrCreateCert(ICacheEntry entry) X509Certificate2 GetOrCreateCert(ICacheEntry entry)
@ -138,17 +186,14 @@ namespace FastGithub
yield return host; yield return host;
} }
yield return Environment.MachineName; var globalPropreties = IPGlobalProperties.GetIPGlobalProperties();
yield return IPAddress.Loopback.ToString();
foreach (var @interface in NetworkInterface.GetAllNetworkInterfaces()) yield return globalPropreties.HostName;
foreach (var item in globalPropreties.GetUnicastAddresses())
{ {
foreach (var addressInfo in @interface.GetIPProperties().UnicastAddresses) if (item.Address.AddressFamily == AddressFamily.InterNetwork)
{ {
if (addressInfo.Address.AddressFamily == AddressFamily.InterNetwork) yield return item.Address.ToString();
{
yield return addressInfo.Address.ToString();
}
} }
} }
} }

View File

@ -21,11 +21,11 @@ namespace FastGithub
} }
/// <summary> /// <summary>
/// 使用https反向代理中间件 /// 使用反向代理中间件
/// </summary> /// </summary>
/// <param name="app"></param> /// <param name="app"></param>
/// <returns></returns> /// <returns></returns>
public static IApplicationBuilder UseHttpsReverseProxy(this IApplicationBuilder app) public static IApplicationBuilder UseReverseProxy(this IApplicationBuilder app)
{ {
var middleware = app.ApplicationServices.GetRequiredService<ReverseProxyMiddleware>(); var middleware = app.ApplicationServices.GetRequiredService<ReverseProxyMiddleware>();
return app.Use(next => context => middleware.InvokeAsync(context, next)); return app.Use(next => context => middleware.InvokeAsync(context, next));

View File

@ -1,65 +0,0 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.ReverseProxy
{
/// <summary>
/// 反向代理端口检测后台服务
/// </summary>
sealed class ReverseProxyHostedService : IHostedService
{
private readonly ILogger<ReverseProxyHostedService> logger;
/// <summary>
/// 反向代理端口检测后台服务
/// </summary>
/// <param name="logger"></param>
public ReverseProxyHostedService(ILogger<ReverseProxyHostedService> logger)
{
this.logger = logger;
}
/// <summary>
/// 服务启动时
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken)
{
const int HTTPSPORT = 443;
if (OperatingSystem.IsWindows())
{
if (TcpTable.TryGetOwnerProcessId(HTTPSPORT, out var pid))
{
try
{
Process.GetProcessById(pid).Kill();
}
catch (ArgumentException)
{
}
catch (Exception)
{
var processName = Process.GetProcessById(pid).ProcessName;
this.logger.LogError($"由于进程{processName}({pid})占用了{HTTPSPORT}端口,{nameof(FastGithub)}的反向代理无法工作");
}
}
}
return Task.CompletedTask;
}
/// <summary>
/// 服务停止时
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -54,7 +54,8 @@ namespace FastGithub.ReverseProxy
} }
else else
{ {
var destinationPrefix = GetDestinationPrefix(host, domainConfig.Destination); var scheme = context.Request.Scheme;
var destinationPrefix = GetDestinationPrefix(scheme, host, domainConfig.Destination);
var httpClient = this.httpClientFactory.CreateHttpClient(domainConfig); var httpClient = this.httpClientFactory.CreateHttpClient(domainConfig);
var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient); var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient);
await HandleErrorAsync(context, error); await HandleErrorAsync(context, error);
@ -64,12 +65,13 @@ namespace FastGithub.ReverseProxy
/// <summary> /// <summary>
/// 获取目标前缀 /// 获取目标前缀
/// </summary> /// </summary>
/// <param name="scheme"></param>
/// <param name="host"></param> /// <param name="host"></param>
/// <param name="destination"></param> /// <param name="destination"></param>
/// <returns></returns> /// <returns></returns>
private string GetDestinationPrefix(string host, Uri? destination) private string GetDestinationPrefix(string scheme, string host, Uri? destination)
{ {
var defaultValue = $"https://{host}/"; var defaultValue = $"{scheme}://{host}/";
if (destination == null) if (destination == null)
{ {
return defaultValue; return defaultValue;

View File

@ -19,8 +19,7 @@ namespace FastGithub
.AddMemoryCache() .AddMemoryCache()
.AddHttpForwarder() .AddHttpForwarder()
.AddSingleton<RequestLoggingMilldeware>() .AddSingleton<RequestLoggingMilldeware>()
.AddSingleton<ReverseProxyMiddleware>() .AddSingleton<ReverseProxyMiddleware>();
.AddHostedService<ReverseProxyHostedService>();
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -18,6 +19,34 @@ namespace FastGithub.ReverseProxy
[DllImport("iphlpapi.dll", SetLastError = true)] [DllImport("iphlpapi.dll", SetLastError = true)]
private static extern uint GetExtendedTcpTable(void* pTcpTable, ref int pdwSize, bool bOrder, AddressFamily ulAf, TCP_TABLE_CLASS tableClass, uint reserved = 0); private static extern uint GetExtendedTcpTable(void* pTcpTable, ref int pdwSize, bool bOrder, AddressFamily ulAf, TCP_TABLE_CLASS tableClass, uint reserved = 0);
/// <summary>
/// 杀死占用进程
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public static bool KillPortOwner(int port)
{
if (TryGetOwnerProcessId(port, out var pid) == false)
{
return true;
}
try
{
Process.GetProcessById(pid).Kill();
return true;
}
catch (ArgumentException)
{
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary> /// <summary>
/// 获取tcp端口的占用进程id /// 获取tcp端口的占用进程id
/// </summary> /// </summary>

View File

@ -29,7 +29,7 @@ namespace FastGithub
/// <returns></returns> /// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
this.logger.LogInformation($"{nameof(FastGithub)}启动完成访问https://127.0.0.1或本机其它任意ip可进入Dashboard"); this.logger.LogInformation($"{nameof(FastGithub)}启动完成访问http://127.0.0.1或本机其它任意ip可进入Dashboard");
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -41,7 +41,11 @@ namespace FastGithub
.ConfigureWebHostDefaults(webBuilder => .ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>();
webBuilder.UseKestrel(kestrel => kestrel.ListenHttpsReverseProxy()); webBuilder.UseKestrel(kestrel =>
{
kestrel.ListenHttpReverseProxy();
kestrel.ListenHttpsReverseProxy();
});
}); });
} }
} }

View File

@ -43,7 +43,7 @@ namespace FastGithub
public void Configure(IApplicationBuilder app) public void Configure(IApplicationBuilder app)
{ {
app.UseRequestLogging(); app.UseRequestLogging();
app.UseHttpsReverseProxy(); app.UseReverseProxy();
app.UseRouting(); app.UseRouting();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {

View File

@ -11,7 +11,7 @@ github加速神器
如果不能下载[releases](https://github.com/xljiulang/FastGithub/releases)里发布的程序可以到Q群`307306673`里面的群文件下载。 如果不能下载[releases](https://github.com/xljiulang/FastGithub/releases)里发布的程序可以到Q群`307306673`里面的群文件下载。
### 使用说明 ### 使用说明
运行FastGithub然后浏览器访问 https://127.0.0.1 或其它ip进入Dashboard 运行FastGithub然后浏览器访问 http://127.0.0.1 或其它ip进入Dashboard
### 安全性说明 ### 安全性说明
FastGithub生成自签名CA证书要求安装到本机设备。FastGithub为每台不同设备生成的CA不同的证书保存在CACert文件夹下请不要将证书私钥泄露给他人以免造成损失。 FastGithub生成自签名CA证书要求安装到本机设备。FastGithub为每台不同设备生成的CA不同的证书保存在CACert文件夹下请不要将证书私钥泄露给他人以免造成损失。