diff --git a/FastGithub.Core/DnsConfig.cs b/FastGithub.Core/DnsConfig.cs
index c3ae014..a302617 100644
--- a/FastGithub.Core/DnsConfig.cs
+++ b/FastGithub.Core/DnsConfig.cs
@@ -18,7 +18,7 @@ namespace FastGithub
///
/// 端口
///
- public int Port { get; set; }
+ public int Port { get; set; } = 53;
///
/// 转换为IPEndPoint
diff --git a/FastGithub.Core/DomainConfig.cs b/FastGithub.Core/DomainConfig.cs
index 16de37f..aba77b3 100644
--- a/FastGithub.Core/DomainConfig.cs
+++ b/FastGithub.Core/DomainConfig.cs
@@ -1,4 +1,6 @@
-namespace FastGithub
+using System;
+
+namespace FastGithub
{
///
/// 域名配置
@@ -12,9 +14,8 @@
///
/// 目的地
- /// 支持ip或域名
- /// 留空则本域名
+ /// 格式为相对或绝对uri
///
- public string? Destination { get; set; }
+ public Uri? Destination { get; set; }
}
}
diff --git a/FastGithub.Core/FastGithubConfig.cs b/FastGithub.Core/FastGithubConfig.cs
new file mode 100644
index 0000000..7f9e748
--- /dev/null
+++ b/FastGithub.Core/FastGithubConfig.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Net;
+
+namespace FastGithub
+{
+ ///
+ /// FastGithub配置
+ ///
+ public class FastGithubConfig
+ {
+ private readonly Dictionary domainConfigs;
+
+ ///
+ /// 获取信任dns
+ ///
+ public IPEndPoint TrustedDns { get; }
+
+ ///
+ /// 获取非信任dns
+ ///
+ public IPEndPoint UnTrustedDns { get; }
+
+ ///
+ /// FastGithub配置
+ ///
+ ///
+ public FastGithubConfig(FastGithubOptions options)
+ {
+ this.TrustedDns = options.TrustedDns.ToIPEndPoint();
+ this.UnTrustedDns = options.UntrustedDns.ToIPEndPoint();
+ this.domainConfigs = options.DomainConfigs.ToDictionary(kv => new DomainMatch(kv.Key), kv => kv.Value);
+ }
+
+ ///
+ /// 是否匹配指定的域名
+ ///
+ ///
+ ///
+ public bool IsMatch(string domain)
+ {
+ return this.domainConfigs.Keys.Any(item => item.IsMatch(domain));
+ }
+
+ ///
+ /// 尝试获取域名配置
+ ///
+ ///
+ ///
+ ///
+ public bool TryGetDomainConfig(string domain, [MaybeNullWhen(false)] out DomainConfig domainConfig)
+ {
+ var key = this.domainConfigs.Keys.FirstOrDefault(item => item.IsMatch(domain));
+ if (key == null)
+ {
+ domainConfig = default;
+ return false;
+ }
+ return this.domainConfigs.TryGetValue(key, out domainConfig);
+ }
+ }
+}
diff --git a/FastGithub.Core/FastGithubOptions.cs b/FastGithub.Core/FastGithubOptions.cs
index 41c6869..3ab9e7c 100644
--- a/FastGithub.Core/FastGithubOptions.cs
+++ b/FastGithub.Core/FastGithubOptions.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
+using System.Collections.Generic;
namespace FastGithub
{
@@ -10,13 +7,6 @@ namespace FastGithub
///
public class FastGithubOptions
{
- ///
- /// 域名
- ///
- private DomainMatch[]? domainMatches;
- private IPEndPoint? trustedDnsIPEndPoint;
- private IPEndPoint? unTrustedDnsIPEndPoint;
-
///
/// 受信任的dns服务
///
@@ -28,50 +18,29 @@ namespace FastGithub
public DnsConfig UntrustedDns { get; set; } = new DnsConfig { IPAddress = "114.114.114.114", Port = 53 };
///
- /// 代理的域名表达式
+ /// 代理的域名配置
///
- public HashSet DomainPatterns { get; set; } = new();
+ public Dictionary DomainConfigs { get; set; } = new();
+
+
///
- /// 验证选项值
+ /// 初始化选项为配置
///
///
- public void Validate()
+ public void InitConfig()
{
- this.trustedDnsIPEndPoint = this.TrustedDns.ToIPEndPoint();
- this.unTrustedDnsIPEndPoint = this.UntrustedDns.ToIPEndPoint();
- this.domainMatches = this.DomainPatterns.Select(item => new DomainMatch(item)).ToArray();
- }
-
-
- ///
- /// 受信任的dns服务节点
- ///
- public IPEndPoint GetTrustedDns()
- {
- return this.trustedDnsIPEndPoint ?? throw new InvalidOperationException();
+ this.fastGithubConfig = new FastGithubConfig(this);
}
///
- /// 不受信任的dns服务节点
+ /// 配置
///
- public IPEndPoint GetUnTrustedDns()
- {
- return this.unTrustedDnsIPEndPoint ?? throw new InvalidOperationException();
- }
+ private FastGithubConfig? fastGithubConfig;
///
- /// 是否匹配指定的域名
+ /// 获取配置
///
- ///
- ///
- public bool IsMatch(string domain)
- {
- if (this.domainMatches == null)
- {
- throw new InvalidOperationException();
- }
- return this.domainMatches.Any(item => item.IsMatch(domain));
- }
+ public FastGithubConfig Config => this.fastGithubConfig!;
}
}
diff --git a/FastGithub.Dns/DnsServerHostedService.cs b/FastGithub.Dns/DnsServerHostedService.cs
index d4217c2..7d1b7c0 100644
--- a/FastGithub.Dns/DnsServerHostedService.cs
+++ b/FastGithub.Dns/DnsServerHostedService.cs
@@ -80,7 +80,7 @@ namespace FastGithub.Dns
}
this.logger.LogInformation("dns服务启动成功");
- var secondary = IPAddress.Parse(options.CurrentValue.UntrustedDns.IPAddress);
+ var secondary = options.CurrentValue.Config.UnTrustedDns.Address;
this.dnsAddresses = this.SetNameServers(IPAddress.Loopback, secondary);
FlushResolverCache();
diff --git a/FastGithub.Dns/RequestResolver.cs b/FastGithub.Dns/RequestResolver.cs
index 0b526c1..4de57ea 100644
--- a/FastGithub.Dns/RequestResolver.cs
+++ b/FastGithub.Dns/RequestResolver.cs
@@ -32,7 +32,7 @@ namespace FastGithub.Dns
{
this.options = options;
this.logger = logger;
- this.untrustedResolver = new UdpRequestResolver(options.CurrentValue.GetTrustedDns());
+ this.untrustedResolver = new UdpRequestResolver(options.CurrentValue.Config.TrustedDns);
}
///
@@ -56,7 +56,7 @@ namespace FastGithub.Dns
}
var domain = question.Name;
- if (this.options.CurrentValue.IsMatch(domain.ToString()) == true)
+ if (this.options.CurrentValue.Config.IsMatch(domain.ToString()) == true)
{
var localAddress = remoteEndPointRequest.GetLocalAddress() ?? IPAddress.Loopback;
var record = new IPAddressResourceRecord(domain, localAddress, this.ttl);
diff --git a/FastGithub.ReverseProxy/ReverseProxyApplicationBuilderExtensions.cs b/FastGithub.ReverseProxy/ReverseProxyApplicationBuilderExtensions.cs
index d3d9bc8..afa3ae4 100644
--- a/FastGithub.ReverseProxy/ReverseProxyApplicationBuilderExtensions.cs
+++ b/FastGithub.ReverseProxy/ReverseProxyApplicationBuilderExtensions.cs
@@ -1,10 +1,6 @@
using FastGithub.ReverseProxy;
using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
-using System.Net.Http;
-using Yarp.ReverseProxy.Forwarder;
namespace FastGithub
{
@@ -20,39 +16,8 @@ namespace FastGithub
///
public static IApplicationBuilder UseHttpsReverseProxy(this IApplicationBuilder app)
{
- var httpForwarder = app.ApplicationServices.GetRequiredService();
- var httpClientHanlder = app.ApplicationServices.GetRequiredService();
- var options = app.ApplicationServices.GetRequiredService>();
-
- app.Use(next => async context =>
- {
- var host = context.Request.Host.Host;
- if (options.CurrentValue.IsMatch(host) == false)
- {
- await context.Response.WriteAsJsonAsync(new { message = $"不支持以{host}访问" });
- return;
- }
-
- var port = context.Request.Host.Port ?? 443;
- var destinationPrefix = $"https://{host}:{port}/";
- var httpClient = new HttpMessageInvoker(httpClientHanlder, disposeHandler: false);
- var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient);
-
- if (error != ForwarderError.None)
- {
- var errorFeature = context.GetForwarderErrorFeature();
- if (errorFeature != null)
- {
- await context.Response.WriteAsJsonAsync(new
- {
- error = error.ToString(),
- message = errorFeature.Exception?.Message
- });
- }
- }
- });
-
- return app;
+ var middleware = app.ApplicationServices.GetRequiredService();
+ return app.Use(next => context => middleware.InvokeAsync(context));
}
}
}
diff --git a/FastGithub.ReverseProxy/ReverseProxyMiddleware.cs b/FastGithub.ReverseProxy/ReverseProxyMiddleware.cs
new file mode 100644
index 0000000..f4015c0
--- /dev/null
+++ b/FastGithub.ReverseProxy/ReverseProxyMiddleware.cs
@@ -0,0 +1,103 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Yarp.ReverseProxy.Forwarder;
+
+namespace FastGithub.ReverseProxy
+{
+ ///
+ /// 反向代理中间件
+ ///
+ sealed class ReverseProxyMiddleware
+ {
+ private readonly IHttpForwarder httpForwarder;
+ private readonly SniHttpClientHanlder sniHttpClientHanlder;
+ private readonly NoSniHttpClientHanlder noSniHttpClientHanlder;
+ private readonly IOptionsMonitor options;
+
+ public ReverseProxyMiddleware(
+ IHttpForwarder httpForwarder,
+ SniHttpClientHanlder sniHttpClientHanlder,
+ NoSniHttpClientHanlder noSniHttpClientHanlder,
+ IOptionsMonitor options)
+ {
+ this.httpForwarder = httpForwarder;
+ this.sniHttpClientHanlder = sniHttpClientHanlder;
+ this.noSniHttpClientHanlder = noSniHttpClientHanlder;
+ this.options = options;
+ }
+
+ ///
+ /// 处理请求
+ ///
+ ///
+ ///
+ public async Task InvokeAsync(HttpContext context)
+ {
+ var host = context.Request.Host.Host;
+ if (this.options.CurrentValue.Config.TryGetDomainConfig(host, out var domainConfig) == false)
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ error = ForwarderError.NoAvailableDestinations.ToString(),
+ message = $"不支持https反向代理{host}这个域名"
+ });
+ return;
+ }
+
+ var destinationPrefix = GetDestinationPrefix(host, domainConfig.Destination);
+ var httpClient = domainConfig.NoSni
+ ? new HttpMessageInvoker(this.noSniHttpClientHanlder, disposeHandler: false)
+ : new HttpMessageInvoker(this.sniHttpClientHanlder, disposeHandler: false);
+
+ var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient);
+ await ResponseErrorAsync(context, error);
+ }
+
+ ///
+ /// 获取目标前缀
+ ///
+ ///
+ ///
+ ///
+ private static string GetDestinationPrefix(string host, Uri? destination)
+ {
+ var defaultValue = $"https://{host}/";
+ if (destination == null)
+ {
+ return defaultValue;
+ }
+
+ var baseUri = new Uri(defaultValue);
+ return new Uri(baseUri, destination).ToString();
+ }
+
+ ///
+ /// 写入错误信息
+ ///
+ ///
+ ///
+ ///
+ private static async Task ResponseErrorAsync(HttpContext context, ForwarderError error)
+ {
+ if (error == ForwarderError.None)
+ {
+ return;
+ }
+
+ var errorFeature = context.GetForwarderErrorFeature();
+ if (errorFeature == null)
+ {
+ return;
+ }
+
+ await context.Response.WriteAsJsonAsync(new
+ {
+ error = error.ToString(),
+ message = errorFeature.Exception?.Message
+ });
+ }
+ }
+}
diff --git a/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs b/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs
index cbd98ee..4f7c55e 100644
--- a/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs
+++ b/FastGithub.ReverseProxy/ReverseProxyServiceCollectionExtensions.cs
@@ -19,7 +19,9 @@ namespace FastGithub
.AddMemoryCache()
.AddHttpForwarder()
.AddSingleton()
- .AddTransient();
+ .AddTransient()
+ .AddTransient()
+ .AddSingleton();
}
}
}
diff --git a/FastGithub.ReverseProxy/SniHttpClientHanlder.cs b/FastGithub.ReverseProxy/SniHttpClientHanlder.cs
new file mode 100644
index 0000000..c909a5f
--- /dev/null
+++ b/FastGithub.ReverseProxy/SniHttpClientHanlder.cs
@@ -0,0 +1,60 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FastGithub.ReverseProxy
+{
+ ///
+ /// 携带Sni的的HttpClientHandler
+ ///
+ class SniHttpClientHanlder : DelegatingHandler
+ {
+ private readonly DomainResolver domainResolver;
+ private readonly ILogger logger;
+
+ ///
+ /// 携带Sni的HttpClientHandler
+ ///
+ ///
+ public SniHttpClientHanlder(
+ DomainResolver domainResolver,
+ ILogger logger)
+ {
+ this.domainResolver = domainResolver;
+ this.logger = logger;
+
+ this.InnerHandler = new SocketsHttpHandler
+ {
+ Proxy = null,
+ UseProxy = false,
+ AllowAutoRedirect = false,
+ };
+ }
+
+ ///
+ /// 替换域名为ip
+ ///
+ ///
+ ///
+ ///
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ var uri = request.RequestUri;
+ if (uri != null && uri.HostNameType == UriHostNameType.Dns)
+ {
+ var address = await this.domainResolver.ResolveAsync(uri.Host, cancellationToken);
+ this.logger.LogInformation($"[{address}--Sni->{uri.Host}]");
+
+ var builder = new UriBuilder(uri)
+ {
+ Host = address.ToString()
+ };
+ request.RequestUri = builder.Uri;
+ request.Headers.Host = uri.Host;
+ }
+ return await base.SendAsync(request, cancellationToken);
+ }
+ }
+}
diff --git a/FastGithub/Program.cs b/FastGithub/Program.cs
index 3b77699..0abbd6e 100644
--- a/FastGithub/Program.cs
+++ b/FastGithub/Program.cs
@@ -39,7 +39,7 @@ namespace FastGithub
.AddDnscryptProxy()
.AddOptions()
.Bind(ctx.Configuration.GetSection(nameof(FastGithub)))
- .PostConfigure(opt => opt.Validate());
+ .PostConfigure(opt => opt.InitConfig());
})
.ConfigureWebHostDefaults(web =>
{
diff --git a/FastGithub/appsettings.json b/FastGithub/appsettings.json
index 3e75f2e..e61a0d3 100644
--- a/FastGithub/appsettings.json
+++ b/FastGithub/appsettings.json
@@ -1,23 +1,71 @@
{
"FastGithub": {
- "TrustedDns": { // ڽȷ
+ "TrustedDns": { // ڽDomainConfigsȷ
"IPAddress": "127.0.0.1",
"Port": 5533 // 5533ָdnscrypt-proxy
},
- "UnTrustedDns": { // ڽòٶȿ
+ "UnTrustedDns": { // ڽDomainConfigsٶȿ
"IPAddress": "114.114.114.114",
"Port": 53
},
- "DomainPatterns": [ // *ʾ0ַ
- "github.com",
- "githubstatus.com",
- "*.github.com",
- "*.github.io",
- "*.githubapp.com",
- "*.githubassets.com",
- "*.githubusercontent.com",
- "*github*.s3.amazonaws.com"
- ]
+ "DomainConfigs": { // *ʾ0ַ
+ "github.com": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "githubstatus.com": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "*.github.com": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "*.github.io": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "*.githubapp.com": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "*.githubassets.com": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "*.githubusercontent.com": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "*github*.s3.amazonaws.com": {
+ "NoSni": true,
+ "Destination": null
+ },
+ "ajax.googleapis.com": {
+ "NoSni": false,
+ "Destination": "https://gapis.geekzu.org/ajax/"
+ },
+ "fonts.googleapis.com": {
+ "NoSni": false,
+ "Destination": "https://fonts.geekzu.org/"
+ },
+ "themes.googleusercontent.com": {
+ "NoSni": false,
+ "Destination": "https://gapis.geekzu.org/g-themes/"
+ },
+ "fonts.gstatic.com": {
+ "NoSni": false,
+ "Destination": "https://gapis.geekzu.org/g-fonts/"
+ },
+ "secure.gravatar.com": {
+ "NoSni": false,
+ "Destination": "https://sdn.geekzu.org/"
+ },
+ "*.gravatar.com": {
+ "NoSni": false,
+ "Destination": "https://fdn.geekzu.org/"
+ }
+ }
},
"Logging": {
"LogLevel": {