├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ └── bug-----.md ├── .gitignore ├── @dnscrypt-proxy ├── LICENSE ├── dnscrypt-proxy.toml ├── linux-arm64 │ └── dnscrypt-proxy ├── linux-x64 │ └── dnscrypt-proxy ├── osx-arm64 │ └── dnscrypt-proxy ├── osx-x64 │ └── dnscrypt-proxy └── win-x64 │ └── dnscrypt-proxy.exe ├── Directory.Build.props ├── FastGithub.Configuration ├── DomainConfig.cs ├── DomainPattern.cs ├── FastGithub.Configuration.csproj ├── FastGithubConfig.cs ├── FastGithubException.cs ├── FastGithubOptions.cs ├── GlobalListener.cs ├── LoggerExtensions.cs ├── ResponseConfig.cs ├── ServiceCollectionExtensions.cs ├── TlsSniPattern.cs └── TypeConverterBinder.cs ├── FastGithub.DomainResolve ├── DnsClient.cs ├── DnscryptProxy.cs ├── DomainResolveHostedService.cs ├── DomainResolver.cs ├── FastGithub.DomainResolve.csproj ├── IDomainResolver.cs ├── IPAddressService.cs ├── PersistenceService.cs ├── ServiceCollectionExtensions.cs ├── ServiceInstallUtil.cs └── TomlUtil.cs ├── FastGithub.FlowAnalyze ├── DelegatingDuplexPipe.cs ├── DelegatingStream.cs ├── DuplexPipeStreamExtensions.cs ├── FastGithub.FlowAnalyze.csproj ├── FlowAnalyzeDuplexPipe.cs ├── FlowAnalyzeStream.cs ├── FlowAnalyzer.cs ├── FlowStatistics.cs ├── FlowStatisticsContext.cs ├── FlowType.cs ├── IFlowAnalyzer.cs ├── ListenOptionsExtensions.cs ├── ServiceCollectionExtensions.cs └── TaskToApm.cs ├── FastGithub.Http ├── FastGithub.Http.csproj ├── HttpClient.cs ├── HttpClientFactory.cs ├── HttpClientHandler.cs ├── HttpConnectTimeoutException.cs ├── IHttpClientFactory.cs ├── LifeTimeKey.cs ├── LifetimeHttpHandler.cs ├── LifetimeHttpHandlerCleaner.cs ├── RequestContext.cs ├── RequestContextExtensions.cs └── ServiceCollectionExtensions.cs ├── FastGithub.HttpServer ├── ApplicationBuilderExtensions.cs ├── Certs │ ├── CaCertInstallers │ │ ├── CaCertInstallerOfLinux.cs │ │ ├── CaCertInstallerOfLinuxDebian.cs │ │ ├── CaCertInstallerOfLinuxRedHat.cs │ │ ├── CaCertInstallerOfMacOS.cs │ │ └── CaCertInstallerOfWindows.cs │ ├── CertGenerator.cs │ ├── CertService.cs │ └── ICaCertInstaller.cs ├── FastGithub.HttpServer.csproj ├── HttpMiddlewares │ ├── HttpProxyPacMiddleware.cs │ ├── HttpReverseProxyMiddleware.cs │ ├── IRequestLoggingFeature.cs │ └── RequestLoggingMilldeware.cs ├── KestrelServerExtensions.cs ├── ServiceCollectionExtensions.cs ├── TcpMiddlewares │ ├── GithubGitReverseProxyHandler.cs │ ├── GithubSshReverseProxyHandler.cs │ ├── HttpProxyMiddleware.cs │ ├── IHttpProxyFeature.cs │ ├── ProxyProtocol.cs │ ├── TcpReverseProxyHandler.cs │ └── TunnelMiddleware.cs └── TlsMiddlewares │ ├── FakeTlsConnectionFeature.cs │ ├── TlsInvadeMiddleware.cs │ └── TlsRestoreMiddleware.cs ├── FastGithub.PacketIntercept ├── Dns │ ├── DnsInterceptor.cs │ ├── HostsConflictSolver.cs │ └── ProxyConflictSolver.cs ├── DnsInterceptHostedService.cs ├── FastGithub.PacketIntercept.csproj ├── IDnsConflictSolver.cs ├── IDnsInterceptor.cs ├── ITcpInterceptor.cs ├── ServiceCollectionExtensions.cs ├── Tcp │ ├── GitInterceptor.cs │ ├── HttpInterceptor.cs │ ├── HttpsInterceptor.cs │ ├── SshInterceptor.cs │ └── TcpInterceptor.cs └── TcpInterceptHostedService.cs ├── FastGithub.UI ├── AssemblyInfo.cs ├── FastGithub.UI.csproj ├── FlowChart.xaml ├── FlowChart.xaml.cs ├── FlowStatistics.cs ├── IssuesWebbrowser.xaml ├── IssuesWebbrowser.xaml.cs ├── LogLevel.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Program.cs ├── Resource │ ├── LiveCharts.Wpf.dll │ ├── LiveCharts.dll │ ├── Newtonsoft.Json.dll │ └── issue.html ├── UdpLog.cs ├── UdpLogListBox.xaml ├── UdpLogListBox.xaml.cs ├── UdpLogger.cs ├── app.ico └── app.manifest ├── FastGithub.sln ├── FastGithub ├── .config │ └── dotnet-tools.json ├── AppHostedService.cs ├── AppOptions.cs ├── ConsoleUtil.cs ├── FastGithub.csproj ├── ProductionVersion.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── ServiceExtensions.cs ├── Startup.cs ├── app.manifest ├── appsettings.json └── appsettings │ ├── appsettings.amazonaws.json │ ├── appsettings.bootcss.json │ ├── appsettings.fastly.json │ ├── appsettings.github.json │ ├── appsettings.google.json │ ├── appsettings.imgur.json │ ├── appsettings.microsoft.json │ ├── appsettings.packages.json │ └── appsettings.v2ex.json ├── GitVersion.yml ├── LICENSE ├── MacOSXConfig.md ├── README.html ├── README.md ├── Resources └── MacOSXConfig │ ├── KeychainAccess.png │ ├── autoproxy.png │ ├── cmdwin.png │ ├── proxy.png │ └── trust.png ├── docker-compose.yaml ├── pack.sh └── publish.cmd /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug发现与报告 3 | about: 写一份报告来帮助我们改进 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug描述** 11 | Bug的详细描述内容 12 | 13 | **重现步骤** 14 | 1. xxx 15 | 2. yyy 16 | 3. zzz 17 | 18 | 19 | **软件信息** 20 | - 操作系统: [e.g. win10-x64] 21 | - FastGithub: [e.g. v2.0.0] 22 | -------------------------------------------------------------------------------- /@dnscrypt-proxy/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018-2021, Frank Denis 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /@dnscrypt-proxy/linux-arm64/dnscrypt-proxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/@dnscrypt-proxy/linux-arm64/dnscrypt-proxy -------------------------------------------------------------------------------- /@dnscrypt-proxy/linux-x64/dnscrypt-proxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/@dnscrypt-proxy/linux-x64/dnscrypt-proxy -------------------------------------------------------------------------------- /@dnscrypt-proxy/osx-arm64/dnscrypt-proxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/@dnscrypt-proxy/osx-arm64/dnscrypt-proxy -------------------------------------------------------------------------------- /@dnscrypt-proxy/osx-x64/dnscrypt-proxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/@dnscrypt-proxy/osx-x64/dnscrypt-proxy -------------------------------------------------------------------------------- /@dnscrypt-proxy/win-x64/dnscrypt-proxy.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/@dnscrypt-proxy/win-x64/dnscrypt-proxy.exe -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.1.5 4 | enable 5 | net7.0 6 | true 7 | github加速神器 8 | https://github.com/dotnetcore/FastGithub 9 | win-x64 10 | 11 | 12 | 13 | none 14 | false 15 | 16 | 17 | -------------------------------------------------------------------------------- /FastGithub.Configuration/DomainConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace FastGithub.Configuration 5 | { 6 | /// 7 | /// 域名配置 8 | /// 9 | public record DomainConfig 10 | { 11 | /// 12 | /// 是否发送SNI 13 | /// 14 | public bool TlsSni { get; init; } 15 | 16 | /// 17 | /// 自定义SNI值的表达式 18 | /// 19 | public string? TlsSniPattern { get; init; } 20 | 21 | /// 22 | /// 是否忽略服务器证书域名不匹配 23 | /// 当不发送SNI时服务器可能发回域名不匹配的证书 24 | /// 25 | public bool TlsIgnoreNameMismatch { get; init; } 26 | 27 | /// 28 | /// 使用的ip地址 29 | /// 30 | public IPAddress? IPAddress { get; init; } 31 | 32 | /// 33 | /// 请求超时时长 34 | /// 35 | public TimeSpan? Timeout { get; init; } 36 | 37 | /// 38 | /// 目的地 39 | /// 格式为相对或绝对uri 40 | /// 41 | public Uri? Destination { get; init; } 42 | 43 | /// 44 | /// 自定义响应 45 | /// 46 | public ResponseConfig? Response { get; init; } 47 | 48 | /// 49 | /// 获取TlsSniPattern 50 | /// 51 | /// 52 | public TlsSniPattern GetTlsSniPattern() 53 | { 54 | if (this.TlsSni == false) 55 | { 56 | return Configuration.TlsSniPattern.None; 57 | } 58 | if (string.IsNullOrEmpty(this.TlsSniPattern)) 59 | { 60 | return Configuration.TlsSniPattern.Domain; 61 | } 62 | return new TlsSniPattern(this.TlsSniPattern); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FastGithub.Configuration/DomainPattern.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace FastGithub.Configuration 5 | { 6 | /// 7 | /// 表示域名表达式 8 | /// *表示除.之外任意0到多个字符 9 | /// 10 | public class DomainPattern : IComparable 11 | { 12 | private readonly Regex regex; 13 | private readonly string domainPattern; 14 | 15 | /// 16 | /// 域名表达式 17 | /// *表示除.之外任意0到多个字符 18 | /// 19 | /// 域名表达式 20 | public DomainPattern(string domainPattern) 21 | { 22 | this.domainPattern = domainPattern; 23 | var regexPattern = Regex.Escape(domainPattern).Replace(@"\*", @"[^\.]*"); 24 | this.regex = new Regex($"^{regexPattern}$", RegexOptions.IgnoreCase); 25 | } 26 | 27 | /// 28 | /// 与目标比较 29 | /// 30 | /// 31 | /// 32 | public int CompareTo(DomainPattern? other) 33 | { 34 | if (other is null) 35 | { 36 | return 1; 37 | } 38 | 39 | var segmentsX = this.domainPattern.Split('.'); 40 | var segmentsY = other.domainPattern.Split('.'); 41 | var value = segmentsX.Length - segmentsY.Length; 42 | if (value != 0) 43 | { 44 | return value; 45 | } 46 | 47 | for (var i = segmentsX.Length - 1; i >= 0; i--) 48 | { 49 | var x = segmentsX[i]; 50 | var y = segmentsY[i]; 51 | 52 | value = Compare(x, y); 53 | if (value == 0) 54 | { 55 | continue; 56 | } 57 | return value; 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | 64 | /// 65 | /// 比较两个分段 66 | /// 67 | /// abc 68 | /// abc* 69 | /// 70 | private static int Compare(string x, string y) 71 | { 72 | var valueX = x.Replace('*', char.MaxValue); 73 | var valueY = y.Replace('*', char.MaxValue); 74 | return valueX.CompareTo(valueY); 75 | } 76 | 77 | /// 78 | /// 是否与指定域名匹配 79 | /// 80 | /// 81 | /// 82 | public bool IsMatch(string domain) 83 | { 84 | return this.regex.IsMatch(domain); 85 | } 86 | 87 | /// 88 | /// 转换为文本 89 | /// 90 | /// 91 | public override string ToString() 92 | { 93 | return this.domainPattern; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /FastGithub.Configuration/FastGithub.Configuration.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FastGithub.Configuration/FastGithubConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq; 6 | using System.Net; 7 | 8 | namespace FastGithub.Configuration 9 | { 10 | /// 11 | /// FastGithub配置 12 | /// 13 | public class FastGithubConfig 14 | { 15 | private SortedDictionary domainConfigs; 16 | private ConcurrentDictionary domainConfigCache; 17 | 18 | /// 19 | /// http代理端口 20 | /// 21 | public int HttpProxyPort { get; set; } 22 | 23 | /// 24 | /// 回退的dns 25 | /// 26 | public IPEndPoint[] FallbackDns { get; set; } 27 | 28 | /// 29 | /// FastGithub配置 30 | /// 31 | /// 32 | /// 33 | public FastGithubConfig(IOptionsMonitor options) 34 | { 35 | var opt = options.CurrentValue; 36 | 37 | this.HttpProxyPort = opt.HttpProxyPort; 38 | this.FallbackDns = opt.FallbackDns; 39 | this.domainConfigs = ConvertDomainConfigs(opt.DomainConfigs); 40 | this.domainConfigCache = new ConcurrentDictionary(); 41 | 42 | options.OnChange(opt => this.Update(opt)); 43 | } 44 | 45 | /// 46 | /// 更新配置 47 | /// 48 | /// 49 | private void Update(FastGithubOptions options) 50 | { 51 | this.HttpProxyPort = options.HttpProxyPort; 52 | this.FallbackDns = options.FallbackDns; 53 | this.domainConfigs = ConvertDomainConfigs(options.DomainConfigs); 54 | this.domainConfigCache = new ConcurrentDictionary(); 55 | } 56 | 57 | /// 58 | /// 配置转换 59 | /// 60 | /// 61 | /// 62 | private static SortedDictionary ConvertDomainConfigs(Dictionary domainConfigs) 63 | { 64 | var result = new SortedDictionary(); 65 | foreach (var kv in domainConfigs) 66 | { 67 | result.Add(new DomainPattern(kv.Key), kv.Value); 68 | } 69 | return result; 70 | } 71 | 72 | /// 73 | /// 是否匹配指定的域名 74 | /// 75 | /// 76 | /// 77 | public bool IsMatch(string domain) 78 | { 79 | return this.TryGetDomainConfig(domain, out _); 80 | } 81 | 82 | /// 83 | /// 尝试获取域名配置 84 | /// 85 | /// 86 | /// 87 | /// 88 | public bool TryGetDomainConfig(string domain, [MaybeNullWhen(false)] out DomainConfig value) 89 | { 90 | value = this.domainConfigCache.GetOrAdd(domain, GetDomainConfig); 91 | return value != null; 92 | 93 | DomainConfig? GetDomainConfig(string domain) 94 | { 95 | var key = this.domainConfigs.Keys.FirstOrDefault(item => item.IsMatch(domain)); 96 | return key == null ? null : this.domainConfigs[key]; 97 | } 98 | } 99 | 100 | /// 101 | /// 获取所有域名表达式 102 | /// 103 | /// 104 | public DomainPattern[] GetDomainPatterns() 105 | { 106 | return this.domainConfigs.Keys.ToArray(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /FastGithub.Configuration/FastGithubException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FastGithub.Configuration 4 | { 5 | /// 6 | /// 表示FastGithub异常 7 | /// 8 | public class FastGithubException : Exception 9 | { 10 | /// 11 | /// FastGithub异常 12 | /// 13 | /// 14 | public FastGithubException(string message) 15 | : base(message) 16 | { 17 | } 18 | 19 | /// 20 | /// FastGithub异常 21 | /// 22 | /// 23 | /// 24 | public FastGithubException(string message, Exception? innerException) 25 | : base(message, innerException) 26 | { 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FastGithub.Configuration/FastGithubOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | 5 | namespace FastGithub.Configuration 6 | { 7 | /// 8 | /// FastGithub的配置 9 | /// 10 | public class FastGithubOptions 11 | { 12 | /// 13 | /// http代理端口 14 | /// 15 | public int HttpProxyPort { get; set; } = 38457; 16 | 17 | /// 18 | /// 回退的dns 19 | /// 20 | public IPEndPoint[] FallbackDns { get; set; } = Array.Empty(); 21 | 22 | /// 23 | /// 代理的域名配置 24 | /// 25 | public Dictionary DomainConfigs { get; set; } = new(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FastGithub.Configuration/GlobalListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.NetworkInformation; 5 | 6 | namespace FastGithub.Configuration 7 | { 8 | /// 9 | /// 监听器 10 | /// 11 | public static class GlobalListener 12 | { 13 | private static readonly IPGlobalProperties global = IPGlobalProperties.GetIPGlobalProperties(); 14 | private static readonly HashSet tcpListenPorts = GetListenPorts(global.GetActiveTcpListeners); 15 | private static readonly HashSet udpListenPorts = GetListenPorts(global.GetActiveUdpListeners); 16 | 17 | /// 18 | /// ssh端口 19 | /// 20 | public static int SshPort { get; } = GetAvailableTcpPort(22); 21 | 22 | /// 23 | /// git端口 24 | /// 25 | public static int GitPort { get; } = GetAvailableTcpPort(9418); 26 | 27 | /// 28 | /// http端口 29 | /// 30 | public static int HttpPort { get; } = OperatingSystem.IsWindows() ? GetAvailableTcpPort(80) : GetAvailableTcpPort(3880); 31 | 32 | /// 33 | /// https端口 34 | /// 35 | public static int HttpsPort { get; } = OperatingSystem.IsWindows() ? GetAvailableTcpPort(443) : GetAvailableTcpPort(38443); 36 | 37 | /// 38 | /// 获取已监听的端口 39 | /// 40 | /// 41 | /// 42 | private static HashSet GetListenPorts(Func func) 43 | { 44 | var hashSet = new HashSet(); 45 | try 46 | { 47 | foreach (var endpoint in func()) 48 | { 49 | hashSet.Add(endpoint.Port); 50 | } 51 | } 52 | catch (Exception) 53 | { 54 | } 55 | return hashSet; 56 | } 57 | 58 | /// 59 | /// 是可以监听TCP 60 | /// 61 | /// 62 | /// 63 | public static bool CanListenTcp(int port) 64 | { 65 | return tcpListenPorts.Contains(port) == false; 66 | } 67 | 68 | /// 69 | /// 是可以监听UDP 70 | /// 71 | /// 72 | /// 73 | public static bool CanListenUdp(int port) 74 | { 75 | return udpListenPorts.Contains(port) == false; 76 | } 77 | 78 | /// 79 | /// 是可以监听TCP和Udp 80 | /// 81 | /// 82 | /// 83 | public static bool CanListen(int port) 84 | { 85 | return CanListenTcp(port) && CanListenUdp(port); 86 | } 87 | 88 | /// 89 | /// 获取可用的随机Tcp端口 90 | /// 91 | /// 92 | /// 93 | public static int GetAvailableTcpPort(int minPort) 94 | { 95 | return GetAvailablePort(CanListenTcp, minPort); 96 | } 97 | 98 | /// 99 | /// 获取可用的随机Udp端口 100 | /// 101 | /// 102 | /// 103 | public static int GetAvailableUdpPort(int minPort) 104 | { 105 | return GetAvailablePort(CanListenUdp, minPort); 106 | } 107 | 108 | /// 109 | /// 获取可用的随机端口 110 | /// 111 | /// 112 | /// 113 | public static int GetAvailablePort(int minPort) 114 | { 115 | return GetAvailablePort(CanListen, minPort); 116 | } 117 | 118 | /// 119 | /// 获取可用端口 120 | /// 121 | /// 122 | /// 123 | /// 124 | /// 125 | private static int GetAvailablePort(Func canFunc, int minPort) 126 | { 127 | for (var port = minPort; port < IPEndPoint.MaxPort; port++) 128 | { 129 | if (canFunc(port) == true) 130 | { 131 | return port; 132 | } 133 | } 134 | throw new FastGithubException("当前无可用的端口"); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /FastGithub.Configuration/LoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | 4 | namespace FastGithub 5 | { 6 | /// 7 | /// 日志插值字符串扩展 8 | /// 9 | public static class LoggerExtensions 10 | { 11 | /// 12 | /// 输出日志 13 | /// 14 | /// 15 | /// 16 | /// 17 | public static void Log(this ILogger logger, LogLevel level, FormattableString formattableString) 18 | => logger.Log(level, formattableString.Format, formattableString.GetArguments()); 19 | 20 | /// 21 | /// 输出日志 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | public static void Log(this ILogger logger, LogLevel level, Exception? error, FormattableString formattableString) 28 | => logger.Log(level, error, formattableString.Format, formattableString.GetArguments()); 29 | 30 | /// 31 | /// 输出Trace日志 32 | /// 33 | /// 34 | /// 35 | public static void LogTrace(this ILogger logger, FormattableString formattableString) 36 | => logger.Log(LogLevel.Trace, formattableString); 37 | 38 | /// 39 | /// 输出Debug日志 40 | /// 41 | /// 42 | /// 43 | public static void LogDebug(this ILogger logger, FormattableString formattableString) 44 | => logger.Log(LogLevel.Debug, formattableString); 45 | 46 | /// 47 | /// 输出Information日志 48 | /// 49 | /// 50 | /// 51 | public static void LogInformation(this ILogger logger, FormattableString formattableString) 52 | => logger.Log(LogLevel.Information, formattableString); 53 | 54 | /// 55 | /// 输出Warning日志 56 | /// 57 | /// 58 | /// 59 | public static void LogWarning(this ILogger logger, FormattableString formattableString) 60 | => logger.Log(LogLevel.Warning, formattableString); 61 | 62 | /// 63 | /// 输出日志 64 | /// 65 | /// 66 | /// 67 | public static void LogError(this ILogger logger, FormattableString formattableString) 68 | => logger.Log(LogLevel.Error, formattableString); 69 | 70 | /// 71 | /// 输出日志 72 | /// 73 | /// 74 | /// 75 | public static void LogError(this ILogger logger, Exception error, FormattableString formattableString) 76 | => logger.Log(LogLevel.Error, error, formattableString); 77 | 78 | /// 79 | /// 输出Critical日志 80 | /// 81 | /// 82 | /// 83 | public static void LogCritical(this ILogger logger, FormattableString formattableString) 84 | => logger.Log(LogLevel.Critical, formattableString); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /FastGithub.Configuration/ResponseConfig.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.Configuration 2 | { 3 | /// 4 | /// 响应配置 5 | /// 6 | public record ResponseConfig 7 | { 8 | /// 9 | /// 状态码 10 | /// 11 | public int StatusCode { get; init; } = 200; 12 | 13 | /// 14 | /// 内容类型 15 | /// 16 | public string ContentType { get; init; } = "text/plain;charset=utf-8"; 17 | 18 | /// 19 | /// 内容的值 20 | /// 21 | public string? ContentValue { get; init; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FastGithub.Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using System.Net; 5 | 6 | namespace FastGithub 7 | { 8 | /// 9 | /// 服务注册扩展 10 | /// 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// 添加配置服务 15 | /// 16 | /// 17 | /// 18 | public static IServiceCollection AddConfiguration(this IServiceCollection services) 19 | { 20 | TypeConverterBinder.Bind(val => IPAddress.Parse(val), val => val?.ToString()); 21 | TypeConverterBinder.Bind(val => IPEndPoint.Parse(val), val => val?.ToString()); 22 | 23 | services.TryAddSingleton(); 24 | return services; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FastGithub.Configuration/TlsSniPattern.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace FastGithub.Configuration 5 | { 6 | /// 7 | /// Sni自定义值表达式 8 | /// @domain变量表示取域名值 9 | /// @ipadress变量表示取ip 10 | /// @random变量表示取随机值 11 | /// 12 | public struct TlsSniPattern 13 | { 14 | /// 15 | /// 获取表示式值 16 | /// 17 | public string Value { get; } 18 | 19 | /// 20 | /// 无SNI 21 | /// 22 | public static TlsSniPattern None { get; } = new TlsSniPattern(string.Empty); 23 | 24 | /// 25 | /// 域名SNI 26 | /// 27 | public static TlsSniPattern Domain { get; } = new TlsSniPattern("@domain"); 28 | 29 | /// 30 | /// IP值的SNI 31 | /// 32 | public static TlsSniPattern IPAddress { get; } = new TlsSniPattern("@ipaddress"); 33 | 34 | /// 35 | /// 随机值的SNI 36 | /// 37 | public static TlsSniPattern Random { get; } = new TlsSniPattern("@random"); 38 | 39 | /// 40 | /// Sni自定义值表达式 41 | /// 42 | /// 表示式值 43 | public TlsSniPattern(string? value) 44 | { 45 | this.Value = value ?? string.Empty; 46 | } 47 | 48 | /// 49 | /// 更新域名 50 | /// 51 | /// 52 | public TlsSniPattern WithDomain(string domain) 53 | { 54 | var value = this.Value.Replace(Domain.Value, domain, StringComparison.OrdinalIgnoreCase); 55 | return new TlsSniPattern(value); 56 | } 57 | 58 | /// 59 | /// 更新ip地址 60 | /// 61 | /// 62 | public TlsSniPattern WithIPAddress(IPAddress address) 63 | { 64 | var value = this.Value.Replace(IPAddress.Value, address.ToString(), StringComparison.OrdinalIgnoreCase); 65 | return new TlsSniPattern(value); 66 | } 67 | 68 | /// 69 | /// 更新随机数 70 | /// 71 | public TlsSniPattern WithRandom() 72 | { 73 | var value = this.Value.Replace(Random.Value, Environment.TickCount64.ToString(), StringComparison.OrdinalIgnoreCase); 74 | return new TlsSniPattern(value); 75 | } 76 | 77 | /// 78 | /// 转换为文本 79 | /// 80 | /// 81 | public override string ToString() 82 | { 83 | return this.Value; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /FastGithub.Configuration/TypeConverterBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Globalization; 6 | 7 | namespace FastGithub.Configuration 8 | { 9 | /// 10 | /// TypeConverter类型转换绑定器 11 | /// 12 | static class TypeConverterBinder 13 | { 14 | private static readonly Dictionary binders = new(); 15 | 16 | /// 17 | /// 绑定转换器到指定类型 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static void Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(Func reader, Func writer) 23 | { 24 | binders[typeof(T)] = new Binder(reader, writer); 25 | 26 | var converterType = typeof(TypeConverter<>).MakeGenericType(typeof(T)); 27 | if (TypeDescriptor.GetConverter(typeof(T)).GetType() != converterType) 28 | { 29 | TypeDescriptor.AddAttributes(typeof(T), new TypeConverterAttribute(converterType)); 30 | } 31 | } 32 | 33 | private abstract class Binder 34 | { 35 | public abstract object? Read(string value); 36 | 37 | public abstract string? Write(object? value); 38 | } 39 | 40 | 41 | private class Binder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : Binder 42 | { 43 | private readonly Func reader; 44 | private readonly Func writer; 45 | 46 | public Binder(Func reader, Func writer) 47 | { 48 | this.reader = reader; 49 | this.writer = writer; 50 | } 51 | 52 | public override object? Read(string value) 53 | { 54 | return this.reader(value); 55 | } 56 | 57 | public override string? Write(object? value) 58 | { 59 | return this.writer((T?)value); 60 | } 61 | } 62 | 63 | 64 | private class TypeConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : TypeConverter 65 | { 66 | public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) 67 | { 68 | return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 69 | } 70 | 71 | public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) 72 | { 73 | if (value is string stringVal) 74 | { 75 | if (stringVal.Equals(string.Empty)) 76 | { 77 | return default(T); 78 | } 79 | else if (binders.TryGetValue(typeof(T), out var binder)) 80 | { 81 | return binder.Read(stringVal); 82 | } 83 | } 84 | return base.ConvertFrom(context, culture, value); 85 | } 86 | 87 | public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) 88 | { 89 | return destinationType == typeof(T) && binders.TryGetValue(destinationType, out var binder) 90 | ? binder.Write(value) 91 | : base.ConvertTo(context, culture, value, destinationType); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /FastGithub.DomainResolve/DomainResolveHostedService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace FastGithub.DomainResolve 8 | { 9 | /// 10 | /// 域名解析后台服务 11 | /// 12 | sealed class DomainResolveHostedService : BackgroundService 13 | { 14 | private readonly DnscryptProxy dnscryptProxy; 15 | private readonly IDomainResolver domainResolver; 16 | private readonly ILogger logger; 17 | private readonly TimeSpan dnscryptProxyInitDelay = TimeSpan.FromSeconds(5d); 18 | private readonly TimeSpan testPeriodTimeSpan = TimeSpan.FromSeconds(1d); 19 | 20 | /// 21 | /// 域名解析后台服务 22 | /// 23 | /// 24 | /// 25 | public DomainResolveHostedService( 26 | DnscryptProxy dnscryptProxy, 27 | IDomainResolver domainResolver, 28 | ILogger logger) 29 | { 30 | this.dnscryptProxy = dnscryptProxy; 31 | this.domainResolver = domainResolver; 32 | this.logger = logger; 33 | } 34 | 35 | /// 36 | /// 后台任务 37 | /// 38 | /// 39 | /// 40 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 41 | { 42 | try 43 | { 44 | await this.dnscryptProxy.StartAsync(stoppingToken); 45 | await Task.Delay(dnscryptProxyInitDelay, stoppingToken); 46 | 47 | while (stoppingToken.IsCancellationRequested == false) 48 | { 49 | await this.domainResolver.TestSpeedAsync(stoppingToken); 50 | await Task.Delay(this.testPeriodTimeSpan, stoppingToken); 51 | } 52 | } 53 | catch (OperationCanceledException) 54 | { 55 | } 56 | catch (Exception ex) 57 | { 58 | this.logger.LogError(ex, "域名解析异常"); 59 | } 60 | } 61 | 62 | /// 63 | /// 停止服务 64 | /// 65 | /// 66 | /// 67 | public override Task StopAsync(CancellationToken cancellationToken) 68 | { 69 | this.dnscryptProxy.Stop(); 70 | return base.StopAsync(cancellationToken); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /FastGithub.DomainResolve/DomainResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Runtime.CompilerServices; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace FastGithub.DomainResolve 12 | { 13 | /// 14 | /// 域名解析器 15 | /// 16 | sealed class DomainResolver : IDomainResolver 17 | { 18 | private const int MAX_IP_COUNT = 3; 19 | private readonly DnsClient dnsClient; 20 | private readonly PersistenceService persistence; 21 | private readonly IPAddressService addressService; 22 | private readonly ILogger logger; 23 | private readonly ConcurrentDictionary dnsEndPointAddress = new(); 24 | 25 | /// 26 | /// 域名解析器 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | public DomainResolver( 33 | DnsClient dnsClient, 34 | PersistenceService persistence, 35 | IPAddressService addressService, 36 | ILogger logger) 37 | { 38 | this.dnsClient = dnsClient; 39 | this.persistence = persistence; 40 | this.addressService = addressService; 41 | this.logger = logger; 42 | 43 | foreach (var endPoint in persistence.ReadDnsEndPoints()) 44 | { 45 | this.dnsEndPointAddress.TryAdd(endPoint, Array.Empty()); 46 | } 47 | } 48 | 49 | /// 50 | /// 解析域名 51 | /// 52 | /// 节点 53 | /// 54 | /// 55 | public async IAsyncEnumerable ResolveAsync(DnsEndPoint endPoint, [EnumeratorCancellation] CancellationToken cancellationToken) 56 | { 57 | if (this.dnsEndPointAddress.TryGetValue(endPoint, out var addresses) && addresses.Length > 0) 58 | { 59 | foreach (var address in addresses) 60 | { 61 | yield return address; 62 | } 63 | } 64 | else 65 | { 66 | if (this.dnsEndPointAddress.TryAdd(endPoint, Array.Empty())) 67 | { 68 | await this.persistence.WriteDnsEndPointsAsync(this.dnsEndPointAddress.Keys, cancellationToken); 69 | } 70 | 71 | await foreach (var adddress in this.dnsClient.ResolveAsync(endPoint, fastSort: true, cancellationToken)) 72 | { 73 | yield return adddress; 74 | } 75 | } 76 | } 77 | 78 | /// 79 | /// 对所有节点进行测速 80 | /// 81 | /// 82 | /// 83 | public async Task TestSpeedAsync(CancellationToken cancellationToken) 84 | { 85 | foreach (var keyValue in this.dnsEndPointAddress.OrderBy(item => item.Value.Length)) 86 | { 87 | var dnsEndPoint = keyValue.Key; 88 | var oldAddresses = keyValue.Value; 89 | 90 | var newAddresses = await this.addressService.GetAddressesAsync(dnsEndPoint, oldAddresses, cancellationToken); 91 | this.dnsEndPointAddress[dnsEndPoint] = newAddresses; 92 | 93 | var oldSegmentums = oldAddresses.Take(MAX_IP_COUNT); 94 | var newSegmentums = newAddresses.Take(MAX_IP_COUNT); 95 | if (oldSegmentums.SequenceEqual(newSegmentums) == false) 96 | { 97 | var addressArray = string.Join(", ", newSegmentums.Select(item => item.ToString())); 98 | this.logger.LogInformation($"{dnsEndPoint.Host}:{dnsEndPoint.Port}->[{addressArray}]"); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /FastGithub.DomainResolve/FastGithub.DomainResolve.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | PreserveNewest 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /FastGithub.DomainResolve/IDomainResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace FastGithub.DomainResolve 7 | { 8 | /// 9 | /// 域名解析器 10 | /// 11 | public interface IDomainResolver 12 | { 13 | /// 14 | /// 解析所有ip 15 | /// 16 | /// 节点 17 | /// 18 | /// 19 | IAsyncEnumerable ResolveAsync(DnsEndPoint endPoint, CancellationToken cancellationToken = default); 20 | 21 | /// 22 | /// 对所有节点进行测速 23 | /// 24 | /// 25 | /// 26 | Task TestSpeedAsync(CancellationToken cancellationToken = default); 27 | } 28 | } -------------------------------------------------------------------------------- /FastGithub.DomainResolve/PersistenceService.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace FastGithub.DomainResolve 14 | { 15 | /// 16 | /// 域名持久化 17 | /// 18 | sealed partial class PersistenceService 19 | { 20 | private static readonly string dataFile = "dnsendpoints.json"; 21 | private static readonly SemaphoreSlim dataLocker = new(1, 1); 22 | 23 | private readonly FastGithubConfig fastGithubConfig; 24 | private readonly ILogger logger; 25 | 26 | 27 | private record EndPointItem(string Host, int Port); 28 | 29 | [JsonSerializable(typeof(EndPointItem[]))] 30 | [JsonSourceGenerationOptions( 31 | WriteIndented = true, 32 | PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] 33 | private partial class EndPointItemsContext : JsonSerializerContext 34 | { 35 | } 36 | 37 | 38 | /// 39 | /// 域名持久化 40 | /// 41 | /// 42 | /// 43 | public PersistenceService( 44 | FastGithubConfig fastGithubConfig, 45 | ILogger logger) 46 | { 47 | this.fastGithubConfig = fastGithubConfig; 48 | this.logger = logger; 49 | } 50 | 51 | 52 | /// 53 | /// 读取保存的节点 54 | /// 55 | /// 56 | public IList ReadDnsEndPoints() 57 | { 58 | if (File.Exists(dataFile) == false) 59 | { 60 | return Array.Empty(); 61 | } 62 | 63 | try 64 | { 65 | dataLocker.Wait(); 66 | 67 | var utf8Json = File.ReadAllBytes(dataFile); 68 | var endPointItems = JsonSerializer.Deserialize(utf8Json, EndPointItemsContext.Default.EndPointItemArray); 69 | if (endPointItems == null) 70 | { 71 | return Array.Empty(); 72 | } 73 | 74 | var dnsEndPoints = new List(); 75 | foreach (var item in endPointItems) 76 | { 77 | if (this.fastGithubConfig.IsMatch(item.Host) == true) 78 | { 79 | dnsEndPoints.Add(new DnsEndPoint(item.Host, item.Port)); 80 | } 81 | } 82 | return dnsEndPoints; 83 | } 84 | catch (Exception ex) 85 | { 86 | this.logger.LogWarning(ex.Message, "读取dns记录异常"); 87 | return Array.Empty(); 88 | } 89 | finally 90 | { 91 | dataLocker.Release(); 92 | } 93 | } 94 | 95 | /// 96 | /// 保存节点到文件 97 | /// 98 | /// 99 | /// 100 | /// 101 | public async Task WriteDnsEndPointsAsync(IEnumerable dnsEndPoints, CancellationToken cancellationToken) 102 | { 103 | try 104 | { 105 | await dataLocker.WaitAsync(CancellationToken.None); 106 | 107 | var endPointItems = dnsEndPoints.Select(item => new EndPointItem(item.Host, item.Port)).ToArray(); 108 | var utf8Json = JsonSerializer.SerializeToUtf8Bytes(endPointItems, EndPointItemsContext.Default.EndPointItemArray); 109 | await File.WriteAllBytesAsync(dataFile, utf8Json, cancellationToken); 110 | } 111 | catch (Exception ex) 112 | { 113 | this.logger.LogWarning(ex.Message, "保存dns记录异常"); 114 | } 115 | finally 116 | { 117 | dataLocker.Release(); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /FastGithub.DomainResolve/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.DomainResolve; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | 5 | namespace FastGithub 6 | { 7 | /// 8 | /// 服务注册扩展 9 | /// 10 | public static class ServiceCollectionExtensions 11 | { 12 | /// 13 | /// 注册域名解析相关服务 14 | /// 15 | /// 16 | /// 17 | public static IServiceCollection AddDomainResolve(this IServiceCollection services) 18 | { 19 | services.TryAddSingleton(); 20 | services.TryAddSingleton(); 21 | services.TryAddSingleton(); 22 | services.TryAddSingleton(); 23 | services.TryAddSingleton(); 24 | services.AddHostedService(); 25 | return services; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FastGithub.DomainResolve/ServiceInstallUtil.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.Versioning; 3 | using static PInvoke.AdvApi32; 4 | 5 | namespace FastGithub.DomainResolve 6 | { 7 | public static class ServiceInstallUtil 8 | { 9 | /// 10 | /// 安装并启动服务 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | [SupportedOSPlatform("windows")] 17 | public static bool InstallAndStartService(string serviceName, string binaryPath, ServiceStartType startType = ServiceStartType.SERVICE_AUTO_START) 18 | { 19 | using var hSCManager = OpenSCManager(null, null, ServiceManagerAccess.SC_MANAGER_ALL_ACCESS); 20 | if (hSCManager.IsInvalid == true) 21 | { 22 | return false; 23 | } 24 | 25 | var hService = OpenService(hSCManager, serviceName, ServiceAccess.SERVICE_ALL_ACCESS); 26 | if (hService.IsInvalid == true) 27 | { 28 | hService = CreateService( 29 | hSCManager, 30 | serviceName, 31 | serviceName, 32 | ServiceAccess.SERVICE_ALL_ACCESS, 33 | ServiceType.SERVICE_WIN32_OWN_PROCESS, 34 | startType, 35 | ServiceErrorControl.SERVICE_ERROR_NORMAL, 36 | Path.GetFullPath(binaryPath), 37 | lpLoadOrderGroup: null, 38 | lpdwTagId: 0, 39 | lpDependencies: null, 40 | lpServiceStartName: null, 41 | lpPassword: null); 42 | } 43 | 44 | if (hService.IsInvalid == true) 45 | { 46 | return false; 47 | } 48 | 49 | using (hService) 50 | { 51 | return StartService(hService, 0, null); 52 | } 53 | } 54 | 55 | /// 56 | /// 停止并删除服务 57 | /// 58 | /// 59 | /// 60 | [SupportedOSPlatform("windows")] 61 | public static bool StopAndDeleteService(string serviceName) 62 | { 63 | using var hSCManager = OpenSCManager(null, null, ServiceManagerAccess.SC_MANAGER_ALL_ACCESS); 64 | if (hSCManager.IsInvalid == true) 65 | { 66 | return false; 67 | } 68 | 69 | using var hService = OpenService(hSCManager, serviceName, ServiceAccess.SERVICE_ALL_ACCESS); 70 | if (hService.IsInvalid == true) 71 | { 72 | return true; 73 | } 74 | 75 | var status = new SERVICE_STATUS(); 76 | if (QueryServiceStatus(hService, ref status) == true) 77 | { 78 | if (status.dwCurrentState != ServiceState.SERVICE_STOP_PENDING && 79 | status.dwCurrentState != ServiceState.SERVICE_STOPPED) 80 | { 81 | ControlService(hService, ServiceControl.SERVICE_CONTROL_STOP, ref status); 82 | } 83 | } 84 | 85 | return DeleteService(hService); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /FastGithub.DomainResolve/TomlUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Tommy; 8 | 9 | namespace FastGithub.DomainResolve 10 | { 11 | /// 12 | /// doml配置工具 13 | /// 14 | static class TomlUtil 15 | { 16 | /// 17 | /// 设置监听地址 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | public static Task SetListensAsync(string tomlPath, IPEndPoint endpoint, CancellationToken cancellationToken) 24 | { 25 | var value = new TomlArray 26 | { 27 | endpoint.ToString() 28 | }; 29 | return SetAsync(tomlPath, "listen_addresses", value, cancellationToken); 30 | } 31 | 32 | /// 33 | /// 设置日志等级 34 | /// 35 | /// 36 | /// 37 | /// 38 | /// 39 | public static Task SetLogLevelAsync(string tomlPath, int logLevel, CancellationToken cancellationToken) 40 | { 41 | return SetAsync(tomlPath, "log_level", new TomlInteger { Value = logLevel }, cancellationToken); 42 | } 43 | 44 | /// 45 | /// 设置负载均衡模式 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 51 | public static Task SetLBStrategyAsync(string tomlPath, string value, CancellationToken cancellationToken) 52 | { 53 | return SetAsync(tomlPath, "lb_strategy", new TomlString { Value = value }, cancellationToken); 54 | } 55 | 56 | /// 57 | /// 设置TTL 58 | /// 59 | /// 60 | /// 61 | /// 62 | /// 63 | /// 64 | public static async Task SetMinMaxTTLAsync(string tomlPath, TimeSpan minTTL, TimeSpan maxTTL, CancellationToken cancellationToken) 65 | { 66 | var minValue = new TomlInteger { Value = (int)minTTL.TotalSeconds }; 67 | var maxValue = new TomlInteger { Value = (int)maxTTL.TotalSeconds }; 68 | 69 | await SetAsync(tomlPath, "cache_min_ttl", minValue, cancellationToken); 70 | await SetAsync(tomlPath, "cache_neg_min_ttl", minValue, cancellationToken); 71 | await SetAsync(tomlPath, "cache_max_ttl", maxValue, cancellationToken); 72 | await SetAsync(tomlPath, "cache_neg_max_ttl", maxValue, cancellationToken); 73 | } 74 | 75 | /// 76 | /// 设置指定键的值 77 | /// 78 | /// 79 | /// 80 | /// 81 | /// 82 | /// 83 | public static async Task SetAsync(string tomlPath, string key, TomlNode value, CancellationToken cancellationToken) 84 | { 85 | var toml = await File.ReadAllTextAsync(tomlPath, cancellationToken); 86 | var reader = new StringReader(toml); 87 | var tomlTable = TOML.Parse(reader); 88 | tomlTable[key] = value; 89 | 90 | var builder = new StringBuilder(); 91 | var writer = new StringWriter(builder); 92 | tomlTable.WriteTo(writer); 93 | toml = builder.ToString(); 94 | 95 | await File.WriteAllTextAsync(tomlPath, toml, cancellationToken); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/DelegatingDuplexPipe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipelines; 4 | using System.Threading.Tasks; 5 | 6 | namespace FastGithub.FlowAnalyze 7 | { 8 | class DelegatingDuplexPipe : IDuplexPipe, IAsyncDisposable where TDelegatingStream : DelegatingStream 9 | { 10 | private bool disposed; 11 | private readonly object syncRoot = new(); 12 | 13 | public PipeReader Input { get; } 14 | 15 | public PipeWriter Output { get; } 16 | 17 | public DelegatingDuplexPipe(IDuplexPipe duplexPipe, Func delegatingStreamFactory) : 18 | this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), delegatingStreamFactory) 19 | { 20 | } 21 | 22 | public DelegatingDuplexPipe(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func delegatingStreamFactory) 23 | { 24 | var delegatingStream = delegatingStreamFactory(duplexPipe.AsStream()); 25 | this.Input = PipeReader.Create(delegatingStream, readerOptions); 26 | this.Output = PipeWriter.Create(delegatingStream, writerOptions); 27 | } 28 | 29 | public virtual async ValueTask DisposeAsync() 30 | { 31 | lock (this.syncRoot) 32 | { 33 | if (this.disposed == true) 34 | { 35 | return; 36 | } 37 | this.disposed = true; 38 | } 39 | 40 | await this.Input.CompleteAsync(); 41 | await this.Output.CompleteAsync(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/DelegatingStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace FastGithub.FlowAnalyze 7 | { 8 | abstract class DelegatingStream : Stream 9 | { 10 | protected Stream Inner { get; } 11 | 12 | public DelegatingStream(Stream inner) 13 | { 14 | this.Inner = inner; 15 | } 16 | 17 | public override bool CanRead 18 | { 19 | get 20 | { 21 | return this.Inner.CanRead; 22 | } 23 | } 24 | 25 | public override bool CanSeek 26 | { 27 | get 28 | { 29 | return this.Inner.CanSeek; 30 | } 31 | } 32 | 33 | public override bool CanWrite 34 | { 35 | get 36 | { 37 | return this.Inner.CanWrite; 38 | } 39 | } 40 | 41 | public override long Length 42 | { 43 | get 44 | { 45 | return this.Inner.Length; 46 | } 47 | } 48 | 49 | public override long Position 50 | { 51 | get 52 | { 53 | return this.Inner.Position; 54 | } 55 | 56 | set 57 | { 58 | this.Inner.Position = value; 59 | } 60 | } 61 | 62 | public override void Flush() 63 | { 64 | this.Inner.Flush(); 65 | } 66 | 67 | public override Task FlushAsync(CancellationToken cancellationToken) 68 | { 69 | return this.Inner.FlushAsync(cancellationToken); 70 | } 71 | 72 | public override int Read(byte[] buffer, int offset, int count) 73 | { 74 | return this.Inner.Read(buffer, offset, count); 75 | } 76 | 77 | public override int Read(Span destination) 78 | { 79 | return this.Inner.Read(destination); 80 | } 81 | 82 | public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 83 | { 84 | return this.Inner.ReadAsync(buffer, offset, count, cancellationToken); 85 | } 86 | 87 | public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) 88 | { 89 | return this.Inner.ReadAsync(destination, cancellationToken); 90 | } 91 | 92 | public override long Seek(long offset, SeekOrigin origin) 93 | { 94 | return this.Inner.Seek(offset, origin); 95 | } 96 | 97 | public override void SetLength(long value) 98 | { 99 | this.Inner.SetLength(value); 100 | } 101 | 102 | public override void Write(byte[] buffer, int offset, int count) 103 | { 104 | this.Inner.Write(buffer, offset, count); 105 | } 106 | 107 | public override void Write(ReadOnlySpan source) 108 | { 109 | this.Inner.Write(source); 110 | } 111 | 112 | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 113 | { 114 | return this.Inner.WriteAsync(buffer, offset, count, cancellationToken); 115 | } 116 | 117 | public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) 118 | { 119 | return this.Inner.WriteAsync(source, cancellationToken); 120 | } 121 | 122 | public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) 123 | { 124 | return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); 125 | } 126 | 127 | public override int EndRead(IAsyncResult asyncResult) 128 | { 129 | return TaskToApm.End(asyncResult); 130 | } 131 | 132 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) 133 | { 134 | return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); 135 | } 136 | 137 | public override void EndWrite(IAsyncResult asyncResult) 138 | { 139 | TaskToApm.End(asyncResult); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/FastGithub.FlowAnalyze.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/FlowAnalyzeDuplexPipe.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Pipelines; 2 | 3 | namespace FastGithub.FlowAnalyze 4 | { 5 | sealed class FlowAnalyzeDuplexPipe : DelegatingDuplexPipe 6 | { 7 | public FlowAnalyzeDuplexPipe(IDuplexPipe duplexPipe, IFlowAnalyzer flowAnalyzer) : 8 | base(duplexPipe, stream => new FlowAnalyzeStream(stream, flowAnalyzer)) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/FlowAnalyzeStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace FastGithub.FlowAnalyze 7 | { 8 | sealed class FlowAnalyzeStream : DelegatingStream 9 | { 10 | private readonly IFlowAnalyzer flowAnalyzer; 11 | 12 | public FlowAnalyzeStream(Stream inner, IFlowAnalyzer flowAnalyzer) 13 | : base(inner) 14 | { 15 | this.flowAnalyzer = flowAnalyzer; 16 | } 17 | 18 | public override int Read(byte[] buffer, int offset, int count) 19 | { 20 | int read = base.Read(buffer, offset, count); 21 | this.flowAnalyzer.OnFlow(FlowType.Read, read); 22 | return read; 23 | } 24 | 25 | public override int Read(Span destination) 26 | { 27 | int read = base.Read(destination); 28 | this.flowAnalyzer.OnFlow(FlowType.Read, read); 29 | return read; 30 | } 31 | 32 | public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 33 | { 34 | int read = await base.ReadAsync(buffer.AsMemory(offset, count), cancellationToken); 35 | this.flowAnalyzer.OnFlow(FlowType.Read, read); 36 | return read; 37 | } 38 | 39 | public override async ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) 40 | { 41 | int read = await base.ReadAsync(destination, cancellationToken); 42 | this.flowAnalyzer.OnFlow(FlowType.Read, read); 43 | return read; 44 | } 45 | 46 | 47 | public override void Write(byte[] buffer, int offset, int count) 48 | { 49 | this.flowAnalyzer.OnFlow(FlowType.Wirte, count); 50 | base.Write(buffer, offset, count); 51 | } 52 | 53 | public override void Write(ReadOnlySpan source) 54 | { 55 | this.flowAnalyzer.OnFlow(FlowType.Wirte, source.Length); 56 | base.Write(source); 57 | } 58 | 59 | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 60 | { 61 | this.flowAnalyzer.OnFlow(FlowType.Wirte, count); 62 | return base.WriteAsync(buffer, offset, count, cancellationToken); 63 | } 64 | 65 | public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) 66 | { 67 | this.flowAnalyzer.OnFlow(FlowType.Wirte, source.Length); 68 | return base.WriteAsync(source, cancellationToken); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/FlowAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Threading; 5 | 6 | namespace FastGithub.FlowAnalyze 7 | { 8 | sealed class FlowAnalyzer : IFlowAnalyzer 9 | { 10 | private const int INTERVAL_SECONDS = 5; 11 | private readonly FlowQueues readQueues = new(INTERVAL_SECONDS); 12 | private readonly FlowQueues writeQueues = new(INTERVAL_SECONDS); 13 | 14 | /// 15 | /// 收到数据 16 | /// 17 | /// 18 | /// 19 | public void OnFlow(FlowType flowType, int length) 20 | { 21 | if (flowType == FlowType.Read) 22 | { 23 | this.readQueues.OnFlow(length); 24 | } 25 | else 26 | { 27 | this.writeQueues.OnFlow(length); 28 | } 29 | } 30 | 31 | /// 32 | /// 获取流量分析 33 | /// 34 | /// 35 | public FlowStatistics GetFlowStatistics() 36 | { 37 | return new FlowStatistics 38 | { 39 | TotalRead = this.readQueues.TotalBytes, 40 | TotalWrite = this.writeQueues.TotalBytes, 41 | ReadRate = this.readQueues.GetRate(), 42 | WriteRate = this.writeQueues.GetRate() 43 | }; 44 | } 45 | 46 | private class FlowQueues 47 | { 48 | private int cleaning = 0; 49 | private long totalBytes = 0L; 50 | private record QueueItem(long Ticks, int Length); 51 | private readonly ConcurrentQueue queues = new(); 52 | 53 | private readonly int intervalSeconds; 54 | 55 | public long TotalBytes => this.totalBytes; 56 | 57 | public FlowQueues(int intervalSeconds) 58 | { 59 | this.intervalSeconds = intervalSeconds; 60 | } 61 | 62 | public void OnFlow(int length) 63 | { 64 | Interlocked.Add(ref this.totalBytes, length); 65 | this.CleanInvalidRecords(); 66 | this.queues.Enqueue(new QueueItem(Environment.TickCount64, length)); 67 | } 68 | 69 | public double GetRate() 70 | { 71 | this.CleanInvalidRecords(); 72 | return (double)this.queues.Sum(item => item.Length) / this.intervalSeconds; 73 | } 74 | 75 | /// 76 | /// 清除无效记录 77 | /// 78 | /// 79 | private bool CleanInvalidRecords() 80 | { 81 | if (Interlocked.CompareExchange(ref this.cleaning, 1, 0) != 0) 82 | { 83 | return false; 84 | } 85 | 86 | var ticks = Environment.TickCount64; 87 | while (this.queues.TryPeek(out var item)) 88 | { 89 | if (ticks - item.Ticks < this.intervalSeconds * 1000) 90 | { 91 | break; 92 | } 93 | else 94 | { 95 | this.queues.TryDequeue(out _); 96 | } 97 | } 98 | 99 | Interlocked.Exchange(ref this.cleaning, 0); 100 | return true; 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/FlowStatistics.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.FlowAnalyze 2 | { 3 | /// 4 | /// 流量统计 5 | /// 6 | public record FlowStatistics 7 | { 8 | /// 9 | /// 获取总读上行 10 | /// 11 | public long TotalRead { get; init; } 12 | 13 | /// 14 | /// 获取总下行 15 | /// 16 | public long TotalWrite { get; init; } 17 | 18 | /// 19 | /// 获取读取速率 20 | /// 21 | public double ReadRate { get; init; } 22 | 23 | /// 24 | /// 获取写入速率 25 | /// 26 | public double WriteRate { get; init; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/FlowStatisticsContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace FastGithub.FlowAnalyze 4 | { 5 | [JsonSerializable(typeof(FlowStatistics))] 6 | public partial class FlowStatisticsContext : JsonSerializerContext 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/FlowType.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.FlowAnalyze 2 | { 3 | /// 4 | /// 流量类型 5 | /// 6 | public enum FlowType 7 | { 8 | /// 9 | /// 读取 10 | /// 11 | Read, 12 | 13 | /// 14 | /// 写入 15 | /// 16 | Wirte 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/IFlowAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.FlowAnalyze 2 | { 3 | /// 4 | /// 流量分析器 5 | /// 6 | public interface IFlowAnalyzer 7 | { 8 | /// 9 | /// 收到数据 10 | /// 11 | /// 12 | /// 13 | void OnFlow(FlowType flowType, int length); 14 | 15 | /// 16 | /// 获取速率 17 | /// 18 | /// 19 | FlowStatistics GetFlowStatistics(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/ListenOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.FlowAnalyze; 2 | using Microsoft.AspNetCore.Server.Kestrel.Core; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace FastGithub 6 | { 7 | /// 8 | /// ListenOptions扩展 9 | /// 10 | public static class ListenOptionsExtensions 11 | { 12 | /// 13 | /// 使用流量分析中间件 14 | /// 15 | /// 16 | /// 17 | public static ListenOptions UseFlowAnalyze(this ListenOptions listen) 18 | { 19 | var flowAnalyzer = listen.ApplicationServices.GetRequiredService(); 20 | listen.Use(next => async context => 21 | { 22 | var oldTransport = context.Transport; 23 | try 24 | { 25 | await using var loggingDuplexPipe = new FlowAnalyzeDuplexPipe(context.Transport, flowAnalyzer); 26 | context.Transport = loggingDuplexPipe; 27 | await next(context); 28 | } 29 | finally 30 | { 31 | context.Transport = oldTransport; 32 | } 33 | }); 34 | return listen; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FastGithub.FlowAnalyze/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.FlowAnalyze; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace FastGithub 5 | { 6 | /// 7 | /// ServiceCollection扩展 8 | /// 9 | public static class ServiceCollectionExtensions 10 | { 11 | /// 12 | /// 添加流量分析 13 | /// 14 | /// 15 | /// 16 | public static IServiceCollection AddFlowAnalyze(this IServiceCollection services) 17 | { 18 | return services.AddSingleton(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FastGithub.Http/FastGithub.Http.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FastGithub.Http/HttpClient.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using FastGithub.DomainResolve; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace FastGithub.Http 9 | { 10 | /// 11 | /// 表示http客户端 12 | /// 13 | public class HttpClient : HttpMessageInvoker 14 | { 15 | /// 16 | /// 插入的UserAgent标记 17 | /// 18 | private readonly static ProductInfoHeaderValue userAgent = new(new ProductHeaderValue(nameof(FastGithub), "1.0")); 19 | 20 | /// 21 | /// http客户端 22 | /// 23 | /// 24 | /// 25 | public HttpClient(DomainConfig domainConfig, IDomainResolver domainResolver) 26 | : this(new HttpClientHandler(domainConfig, domainResolver), disposeHandler: true) 27 | { 28 | } 29 | 30 | /// 31 | /// http客户端 32 | /// 33 | /// 34 | /// 35 | public HttpClient(HttpMessageHandler handler, bool disposeHandler) 36 | : base(handler, disposeHandler) 37 | { 38 | } 39 | 40 | /// 41 | /// 发送请求 42 | /// 43 | /// 44 | /// 45 | /// 46 | public override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 47 | { 48 | if (request.Headers.UserAgent.Contains(userAgent)) 49 | { 50 | throw new FastGithubException($"由于{request.RequestUri}实际指向了{nameof(FastGithub)}自身,{nameof(FastGithub)}已中断本次转发"); 51 | } 52 | request.Headers.UserAgent.Add(userAgent); 53 | var response = await base.SendAsync(request, cancellationToken); 54 | response.Headers.Server.TryParseAdd(nameof(FastGithub)); 55 | return response; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /FastGithub.Http/HttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using FastGithub.DomainResolve; 3 | using System; 4 | using System.Collections.Concurrent; 5 | 6 | namespace FastGithub.Http 7 | { 8 | /// 9 | /// HttpClient工厂 10 | /// 11 | sealed class HttpClientFactory : IHttpClientFactory 12 | { 13 | private readonly IDomainResolver domainResolver; 14 | 15 | /// 16 | /// 首次生命周期 17 | /// 18 | private readonly TimeSpan firstLiftTime = TimeSpan.FromSeconds(10d); 19 | 20 | /// 21 | /// 非首次生命周期 22 | /// 23 | private readonly TimeSpan nextLifeTime = TimeSpan.FromSeconds(100d); 24 | 25 | /// 26 | /// LifetimeHttpHandler清理器 27 | /// 28 | private readonly LifetimeHttpHandlerCleaner httpHandlerCleaner = new(); 29 | 30 | /// 31 | /// LazyOf(LifetimeHttpHandler)缓存 32 | /// 33 | private readonly ConcurrentDictionary> httpHandlerLazyCache = new(); 34 | 35 | 36 | /// 37 | /// HttpClient工厂 38 | /// 39 | /// 40 | public HttpClientFactory(IDomainResolver domainResolver) 41 | { 42 | this.domainResolver = domainResolver; 43 | } 44 | 45 | /// 46 | /// 创建httpClient 47 | /// 48 | /// 49 | /// 50 | /// 51 | public HttpClient CreateHttpClient(string domain, DomainConfig domainConfig) 52 | { 53 | var lifeTimeKey = new LifeTimeKey(domain, domainConfig); 54 | var lifetimeHttpHandler = this.httpHandlerLazyCache.GetOrAdd(lifeTimeKey, CreateLifetimeHttpHandlerLazy).Value; 55 | return new HttpClient(lifetimeHttpHandler, disposeHandler: false); 56 | 57 | Lazy CreateLifetimeHttpHandlerLazy(LifeTimeKey lifeTimeKey) 58 | { 59 | return new Lazy(() => this.CreateLifetimeHttpHandler(lifeTimeKey, this.firstLiftTime), true); 60 | } 61 | } 62 | 63 | /// 64 | /// 创建LifetimeHttpHandler 65 | /// 66 | /// 67 | /// 68 | /// 69 | private LifetimeHttpHandler CreateLifetimeHttpHandler(LifeTimeKey lifeTimeKey, TimeSpan lifeTime) 70 | { 71 | return new LifetimeHttpHandler(this.domainResolver, lifeTimeKey, lifeTime, this.OnLifetimeHttpHandlerDeactivate); 72 | } 73 | 74 | /// 75 | /// 当有httpHandler失效时 76 | /// 77 | /// httpHandler 78 | private void OnLifetimeHttpHandlerDeactivate(LifetimeHttpHandler lifetimeHttpHandler) 79 | { 80 | var lifeTimeKey = lifetimeHttpHandler.LifeTimeKey; 81 | this.httpHandlerLazyCache[lifeTimeKey] = CreateLifetimeHttpHandlerLazy(lifeTimeKey); 82 | this.httpHandlerCleaner.Add(lifetimeHttpHandler); 83 | 84 | Lazy CreateLifetimeHttpHandlerLazy(LifeTimeKey lifeTimeKey) 85 | { 86 | return new Lazy(() => this.CreateLifetimeHttpHandler(lifeTimeKey, this.nextLifeTime), true); 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /FastGithub.Http/HttpConnectTimeoutException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace FastGithub.Http 5 | { 6 | /// 7 | /// http连接超时异常 8 | /// 9 | sealed class HttpConnectTimeoutException : Exception 10 | { 11 | /// 12 | /// http连接超时异常 13 | /// 14 | /// 连接的ip 15 | public HttpConnectTimeoutException(IPAddress address) 16 | : base(address.ToString()) 17 | { 18 | 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FastGithub.Http/IHttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | 3 | namespace FastGithub.Http 4 | { 5 | /// 6 | /// httpClient工厂 7 | /// 8 | public interface IHttpClientFactory 9 | { 10 | /// 11 | /// 创建httpClient 12 | /// 13 | /// 14 | /// 15 | /// 16 | HttpClient CreateHttpClient(string domain, DomainConfig domainConfig); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FastGithub.Http/LifeTimeKey.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | 3 | namespace FastGithub.Http 4 | { 5 | /// 6 | /// 生命周期的Key 7 | /// 8 | record LifeTimeKey 9 | { 10 | /// 11 | /// 域名 12 | /// 13 | public string Domain { get; } 14 | 15 | /// 16 | /// 域名配置 17 | /// 18 | public DomainConfig DomainConfig { get; } 19 | 20 | /// 21 | /// 生命周期的Key 22 | /// 23 | /// 24 | /// 25 | public LifeTimeKey(string domain, DomainConfig domainConfig) 26 | { 27 | this.Domain = domain; 28 | this.DomainConfig = domainConfig; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FastGithub.Http/LifetimeHttpHandler.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.DomainResolve; 2 | using System; 3 | using System.Net.Http; 4 | using System.Threading; 5 | 6 | namespace FastGithub.Http 7 | { 8 | /// 9 | /// 表示自主管理生命周期的的HttpMessageHandler 10 | /// 11 | sealed class LifetimeHttpHandler : DelegatingHandler 12 | { 13 | private readonly Timer timer; 14 | 15 | public LifeTimeKey LifeTimeKey { get; } 16 | 17 | /// 18 | /// 具有生命周期的HttpHandler 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | public LifetimeHttpHandler(IDomainResolver domainResolver, LifeTimeKey lifeTimeKey, TimeSpan lifeTime, Action deactivateAction) 25 | { 26 | this.LifeTimeKey = lifeTimeKey; 27 | this.InnerHandler = new HttpClientHandler(lifeTimeKey.DomainConfig, domainResolver); 28 | this.timer = new Timer(this.OnTimerCallback, deactivateAction, lifeTime, Timeout.InfiniteTimeSpan); 29 | } 30 | 31 | /// 32 | /// timer触发时 33 | /// 34 | /// 35 | private void OnTimerCallback(object? state) 36 | { 37 | this.timer.Dispose(); 38 | ((Action)(state!))(this); 39 | } 40 | 41 | /// 42 | /// 这里不释放资源 43 | /// 44 | /// 45 | protected override void Dispose(bool disposing) 46 | { 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /FastGithub.Http/LifetimeHttpHandlerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace FastGithub.Http 8 | { 9 | /// 10 | /// 表示LifetimeHttpHandler清理器 11 | /// 12 | sealed class LifetimeHttpHandlerCleaner 13 | { 14 | /// 15 | /// 当前监视生命周期的记录的数量 16 | /// 17 | private int trackingEntryCount = 0; 18 | 19 | /// 20 | /// 监视生命周期的记录队列 21 | /// 22 | private readonly ConcurrentQueue trackingEntries = new(); 23 | 24 | /// 25 | /// 获取或设置清理的时间间隔 26 | /// 默认10s 27 | /// 28 | public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromSeconds(10d); 29 | 30 | /// 31 | /// 添加要清除的httpHandler 32 | /// 33 | /// httpHandler 34 | public void Add(LifetimeHttpHandler handler) 35 | { 36 | var entry = new TrackingEntry(handler); 37 | this.trackingEntries.Enqueue(entry); 38 | 39 | // 从0变为1,要启动清理作业 40 | if (Interlocked.Increment(ref this.trackingEntryCount) == 1) 41 | { 42 | this.StartCleanup(); 43 | } 44 | } 45 | 46 | /// 47 | /// 启动清理作业 48 | /// 49 | private async void StartCleanup() 50 | { 51 | await Task.Yield(); 52 | while (this.Cleanup() == false) 53 | { 54 | await Task.Delay(this.CleanupInterval); 55 | } 56 | } 57 | 58 | /// 59 | /// 清理失效的拦截器 60 | /// 返回是否完全清理 61 | /// 62 | /// 63 | private bool Cleanup() 64 | { 65 | var cleanCount = this.trackingEntries.Count; 66 | for (var i = 0; i < cleanCount; i++) 67 | { 68 | this.trackingEntries.TryDequeue(out var entry); 69 | Debug.Assert(entry != null); 70 | 71 | if (entry.CanDispose == false) 72 | { 73 | this.trackingEntries.Enqueue(entry); 74 | continue; 75 | } 76 | 77 | entry.Dispose(); 78 | if (Interlocked.Decrement(ref this.trackingEntryCount) == 0) 79 | { 80 | return true; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | 87 | /// 88 | /// 表示监视生命周期的记录 89 | /// 90 | private class TrackingEntry : IDisposable 91 | { 92 | /// 93 | /// 用于释放资源的对象 94 | /// 95 | private readonly IDisposable disposable; 96 | 97 | /// 98 | /// 监视对象的弱引用 99 | /// 100 | private readonly WeakReference weakReference; 101 | 102 | /// 103 | /// 获取是否可以释放资源 104 | /// 105 | /// 106 | public bool CanDispose => this.weakReference.IsAlive == false; 107 | 108 | /// 109 | /// 监视生命周期的记录 110 | /// 111 | /// 激活状态的httpHandler 112 | public TrackingEntry(LifetimeHttpHandler handler) 113 | { 114 | this.disposable = handler.InnerHandler!; 115 | this.weakReference = new WeakReference(handler); 116 | } 117 | 118 | /// 119 | /// 释放资源 120 | /// 121 | public void Dispose() 122 | { 123 | try 124 | { 125 | this.disposable.Dispose(); 126 | } 127 | catch (Exception) 128 | { 129 | } 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /FastGithub.Http/RequestContext.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | 3 | namespace FastGithub.Http 4 | { 5 | /// 6 | /// 表示请求上下文 7 | /// 8 | sealed class RequestContext 9 | { 10 | /// 11 | /// 获取或设置是否为https请求 12 | /// 13 | public bool IsHttps { get; } 14 | 15 | /// 16 | /// 获取或设置Sni值 17 | /// 18 | public TlsSniPattern TlsSniValue { get; } 19 | 20 | /// 21 | /// 请求上下文 22 | /// 23 | /// 24 | /// 25 | public RequestContext(bool isHttps, TlsSniPattern tlsSniValue) 26 | { 27 | IsHttps = isHttps; 28 | TlsSniValue = tlsSniValue; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FastGithub.Http/RequestContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace FastGithub.Http 5 | { 6 | /// 7 | /// 请求上下文扩展 8 | /// 9 | static class RequestContextExtensions 10 | { 11 | private static readonly HttpRequestOptionsKey key = new(nameof(RequestContext)); 12 | 13 | /// 14 | /// 设置RequestContext 15 | /// 16 | /// 17 | /// 18 | public static void SetRequestContext(this HttpRequestMessage httpRequestMessage, RequestContext requestContext) 19 | { 20 | httpRequestMessage.Options.Set(key, requestContext); 21 | } 22 | 23 | /// 24 | /// 获取RequestContext 25 | /// 26 | /// 27 | /// 28 | public static RequestContext GetRequestContext(this HttpRequestMessage httpRequestMessage) 29 | { 30 | return httpRequestMessage.Options.TryGetValue(key, out var requestContext) 31 | ? requestContext 32 | : throw new InvalidOperationException($"请先调用{nameof(SetRequestContext)}"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /FastGithub.Http/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Http; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | 5 | namespace FastGithub 6 | { 7 | /// 8 | /// 服务注册扩展 9 | /// 10 | public static class ServiceCollectionExtensions 11 | { 12 | /// 13 | /// 添加HttpClient相关服务 14 | /// 15 | /// 16 | /// 17 | public static IServiceCollection AddHttpClient(this IServiceCollection services) 18 | { 19 | services.TryAddSingleton(); 20 | return services; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.HttpServer.HttpMiddlewares; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace FastGithub 6 | { 7 | /// 8 | /// ApplicationBuilder扩展 9 | /// 10 | public static class ApplicationBuilderExtensions 11 | { 12 | /// 13 | /// 使用http代理策略中间件 14 | /// 15 | /// 16 | /// 17 | public static IApplicationBuilder UseHttpProxyPac(this IApplicationBuilder app) 18 | { 19 | var middleware = app.ApplicationServices.GetRequiredService(); 20 | return app.Use(next => context => middleware.InvokeAsync(context, next)); 21 | } 22 | 23 | /// 24 | /// 使用请求日志中间件 25 | /// 26 | /// 27 | /// 28 | public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder app) 29 | { 30 | var middleware = app.ApplicationServices.GetRequiredService(); 31 | return app.Use(next => context => middleware.InvokeAsync(context, next)); 32 | } 33 | 34 | /// 35 | /// 禁用请求日志中间件 36 | /// 37 | /// 38 | /// 39 | public static IApplicationBuilder DisableRequestLogging(this IApplicationBuilder app) 40 | { 41 | return app.Use(next => context => 42 | { 43 | var loggingFeature = context.Features.Get(); 44 | if (loggingFeature != null) 45 | { 46 | loggingFeature.Enable = false; 47 | } 48 | return next(context); 49 | }); 50 | } 51 | 52 | /// 53 | /// 使用反向代理中间件 54 | /// 55 | /// 56 | /// 57 | public static IApplicationBuilder UseHttpReverseProxy(this IApplicationBuilder app) 58 | { 59 | var middleware = app.ApplicationServices.GetRequiredService(); 60 | return app.Use(next => context => middleware.InvokeAsync(context, next)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinux.cs: -------------------------------------------------------------------------------- 1 | using FastGithub; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace FastGithub.HttpServer.Certs.CaCertInstallers 10 | { 11 | abstract class CaCertInstallerOfLinux : ICaCertInstaller 12 | { 13 | private readonly ILogger logger; 14 | 15 | /// 16 | /// 更新工具文件名 17 | /// 18 | protected abstract string CaCertUpdatePath { get; } 19 | 20 | /// 21 | /// 证书根目录 22 | /// 23 | protected abstract string CaCertStorePath { get; } 24 | 25 | [DllImport("libc", SetLastError = true)] 26 | private static extern uint geteuid(); 27 | 28 | public CaCertInstallerOfLinux(ILogger logger) 29 | { 30 | this.logger = logger; 31 | } 32 | 33 | /// 34 | /// 是否支持 35 | /// 36 | /// 37 | public bool IsSupported() 38 | { 39 | return OperatingSystem.IsLinux() && File.Exists(CaCertUpdatePath); 40 | } 41 | 42 | /// 43 | /// 安装ca证书 44 | /// 45 | /// 证书文件路径 46 | public void Install(string caCertFilePath) 47 | { 48 | var destCertFilePath = Path.Combine(CaCertStorePath, Path.GetFileName(caCertFilePath)); 49 | if (File.Exists(destCertFilePath) && File.ReadAllBytes(caCertFilePath).SequenceEqual(File.ReadAllBytes(destCertFilePath))) 50 | { 51 | return; 52 | } 53 | 54 | if (geteuid() != 0) 55 | { 56 | logger.LogWarning($"无法自动安装CA证书{caCertFilePath}:没有root权限"); 57 | return; 58 | } 59 | 60 | try 61 | { 62 | Directory.CreateDirectory(CaCertStorePath); 63 | foreach (var item in Directory.GetFiles(CaCertStorePath, "fastgithub.*")) 64 | { 65 | File.Delete(item); 66 | } 67 | File.Copy(caCertFilePath, destCertFilePath, overwrite: true); 68 | Process.Start(CaCertUpdatePath).WaitForExit(); 69 | logger.LogInformation($"已自动向系统安装CA证书{caCertFilePath}"); 70 | } 71 | catch (Exception ex) 72 | { 73 | File.Delete(destCertFilePath); 74 | logger.LogWarning(ex.Message, "自动安装CA证书异常"); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxDebian.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace FastGithub.HttpServer.Certs.CaCertInstallers 4 | { 5 | sealed class CaCertInstallerOfLinuxDebian : CaCertInstallerOfLinux 6 | { 7 | protected override string CaCertUpdatePath => "/usr/sbin/update-ca-certificates"; 8 | 9 | protected override string CaCertStorePath => "/usr/local/share/ca-certificates"; 10 | 11 | public CaCertInstallerOfLinuxDebian(ILogger logger) 12 | : base(logger) 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfLinuxRedHat.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace FastGithub.HttpServer.Certs.CaCertInstallers 4 | { 5 | sealed class CaCertInstallerOfLinuxRedHat : CaCertInstallerOfLinux 6 | { 7 | protected override string CaCertUpdatePath => "/usr/bin/update-ca-trust"; 8 | 9 | protected override string CaCertStorePath => "/etc/pki/ca-trust/source/anchors"; 10 | 11 | public CaCertInstallerOfLinuxRedHat(ILogger logger) 12 | : base(logger) 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfMacOS.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | 4 | namespace FastGithub.HttpServer.Certs.CaCertInstallers 5 | { 6 | sealed class CaCertInstallerOfMacOS : ICaCertInstaller 7 | { 8 | private readonly ILogger logger; 9 | 10 | public CaCertInstallerOfMacOS(ILogger logger) 11 | { 12 | this.logger = logger; 13 | } 14 | 15 | /// 16 | /// 是否支持 17 | /// 18 | /// 19 | public bool IsSupported() 20 | { 21 | return OperatingSystem.IsMacOS(); 22 | } 23 | 24 | /// 25 | /// 安装ca证书 26 | /// 27 | /// 证书文件路径 28 | public void Install(string caCertFilePath) 29 | { 30 | logger.LogWarning($"请手动安装CA证书然后设置信任CA证书{caCertFilePath}"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/Certs/CaCertInstallers/CaCertInstallerOfWindows.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Security.Cryptography.X509Certificates; 4 | 5 | namespace FastGithub.HttpServer.Certs.CaCertInstallers 6 | { 7 | sealed class CaCertInstallerOfWindows : ICaCertInstaller 8 | { 9 | private readonly ILogger logger; 10 | 11 | public CaCertInstallerOfWindows(ILogger logger) 12 | { 13 | this.logger = logger; 14 | } 15 | 16 | /// 17 | /// 是否支持 18 | /// 19 | /// 20 | public bool IsSupported() 21 | { 22 | return OperatingSystem.IsWindows(); 23 | } 24 | 25 | /// 26 | /// 安装ca证书 27 | /// 28 | /// 证书文件路径 29 | public void Install(string caCertFilePath) 30 | { 31 | try 32 | { 33 | using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); 34 | store.Open(OpenFlags.ReadWrite); 35 | 36 | var caCert = new X509Certificate2(caCertFilePath); 37 | var subjectName = caCert.Subject[3..]; 38 | foreach (var item in store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false)) 39 | { 40 | if (item.Thumbprint != caCert.Thumbprint) 41 | { 42 | store.Remove(item); 43 | } 44 | } 45 | if (store.Certificates.Find(X509FindType.FindByThumbprint, caCert.Thumbprint, true).Count == 0) 46 | { 47 | store.Add(caCert); 48 | } 49 | store.Close(); 50 | } 51 | catch (Exception) 52 | { 53 | logger.LogWarning($"请手动安装CA证书{caCertFilePath}到“将所有的证书都放入下列存储”\\“受信任的根证书颁发机构”"); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/Certs/ICaCertInstaller.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.HttpServer.Certs 2 | { 3 | /// 4 | /// CA证书安装器 5 | /// 6 | interface ICaCertInstaller 7 | { 8 | /// 9 | /// 是否支持 10 | /// 11 | /// 12 | bool IsSupported(); 13 | 14 | /// 15 | /// 安装ca证书 16 | /// 17 | /// 证书文件路径 18 | void Install(string caCertFilePath); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/FastGithub.HttpServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/HttpMiddlewares/HttpProxyPacMiddleware.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using FastGithub.HttpServer.TcpMiddlewares; 3 | using Microsoft.AspNetCore.Http; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FastGithub.HttpServer.HttpMiddlewares 9 | { 10 | /// 11 | /// http代理策略中间件 12 | /// 13 | sealed class HttpProxyPacMiddleware 14 | { 15 | private readonly FastGithubConfig fastGithubConfig; 16 | 17 | /// 18 | /// http代理策略中间件 19 | /// 20 | /// 21 | public HttpProxyPacMiddleware(FastGithubConfig fastGithubConfig) 22 | { 23 | this.fastGithubConfig = fastGithubConfig; 24 | } 25 | 26 | /// 27 | /// 处理请求 28 | /// 29 | /// 30 | /// 31 | /// 32 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 33 | { 34 | // http请求经过了httpProxy中间件 35 | var proxyFeature = context.Features.Get(); 36 | if (proxyFeature != null && proxyFeature.ProxyProtocol == ProxyProtocol.None) 37 | { 38 | var proxyPac = this.CreateProxyPac(context.Request.Host); 39 | context.Response.ContentType = "application/x-ns-proxy-autoconfig"; 40 | context.Response.Headers.Add("Content-Disposition", $"attachment;filename=proxy.pac"); 41 | await context.Response.WriteAsync(proxyPac); 42 | } 43 | else 44 | { 45 | await next(context); 46 | } 47 | } 48 | 49 | /// 50 | /// 创建proxypac脚本 51 | /// 52 | /// 53 | /// 54 | private string CreateProxyPac(HostString proxyHost) 55 | { 56 | var buidler = new StringBuilder(); 57 | buidler.AppendLine("function FindProxyForURL(url, host){"); 58 | buidler.AppendLine($" var fastgithub = 'PROXY {proxyHost}';"); 59 | foreach (var domain in fastGithubConfig.GetDomainPatterns()) 60 | { 61 | buidler.AppendLine($" if (shExpMatch(host, '{domain}')) return fastgithub;"); 62 | } 63 | buidler.AppendLine(" return 'DIRECT';"); 64 | buidler.AppendLine("}"); 65 | return buidler.ToString(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /FastGithub.HttpServer/HttpMiddlewares/HttpReverseProxyMiddleware.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using FastGithub.Http; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Net; 8 | using System.Threading.Tasks; 9 | using Yarp.ReverseProxy.Forwarder; 10 | 11 | namespace FastGithub.HttpServer.HttpMiddlewares 12 | { 13 | /// 14 | /// 反向代理中间件 15 | /// 16 | sealed class HttpReverseProxyMiddleware 17 | { 18 | private static readonly DomainConfig defaultDomainConfig = new() { TlsSni = true }; 19 | 20 | private readonly IHttpForwarder httpForwarder; 21 | private readonly IHttpClientFactory httpClientFactory; 22 | private readonly FastGithubConfig fastGithubConfig; 23 | private readonly ILogger logger; 24 | 25 | public HttpReverseProxyMiddleware( 26 | IHttpForwarder httpForwarder, 27 | IHttpClientFactory httpClientFactory, 28 | FastGithubConfig fastGithubConfig, 29 | ILogger logger) 30 | { 31 | this.httpForwarder = httpForwarder; 32 | this.httpClientFactory = httpClientFactory; 33 | this.fastGithubConfig = fastGithubConfig; 34 | this.logger = logger; 35 | } 36 | 37 | /// 38 | /// 处理请求 39 | /// 40 | /// 41 | /// 43 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 44 | { 45 | var host = context.Request.Host; 46 | if (TryGetDomainConfig(host, out var domainConfig) == false) 47 | { 48 | await next(context); 49 | } 50 | else if (domainConfig.Response == null) 51 | { 52 | var scheme = context.Request.Scheme; 53 | var destinationPrefix = GetDestinationPrefix(scheme, host, domainConfig.Destination); 54 | var httpClient = httpClientFactory.CreateHttpClient(host.Host, domainConfig); 55 | var error = await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty); 56 | await HandleErrorAsync(context, error); 57 | } 58 | else 59 | { 60 | context.Response.StatusCode = domainConfig.Response.StatusCode; 61 | context.Response.ContentType = domainConfig.Response.ContentType; 62 | if (domainConfig.Response.ContentValue != null) 63 | { 64 | await context.Response.WriteAsync(domainConfig.Response.ContentValue); 65 | } 66 | } 67 | } 68 | 69 | /// 70 | /// 获取域名的DomainConfig 71 | /// 72 | /// 73 | /// 74 | /// 75 | private bool TryGetDomainConfig(HostString host, [MaybeNullWhen(false)] out DomainConfig domainConfig) 76 | { 77 | if (fastGithubConfig.TryGetDomainConfig(host.Host, out domainConfig) == true) 78 | { 79 | return true; 80 | } 81 | 82 | // 未配置的域名,但仍然被解析到本机ip的域名 83 | if (OperatingSystem.IsWindows() && IsDomain(host.Host)) 84 | { 85 | logger.LogWarning($"域名{host.Host}可能已经被DNS污染,如果域名为本机域名,请解析为非回环IP"); 86 | domainConfig = defaultDomainConfig; 87 | return true; 88 | } 89 | 90 | return false; 91 | 92 | // 是否为域名 93 | static bool IsDomain(string host) 94 | { 95 | return IPAddress.TryParse(host, out _) == false && host.Contains('.'); 96 | } 97 | } 98 | 99 | /// 100 | /// 获取目标前缀 101 | /// 102 | /// 103 | /// 104 | /// 105 | /// 106 | private string GetDestinationPrefix(string scheme, HostString host, Uri? destination) 107 | { 108 | var defaultValue = $"{scheme}://{host}/"; 109 | if (destination == null) 110 | { 111 | return defaultValue; 112 | } 113 | 114 | var baseUri = new Uri(defaultValue); 115 | var result = new Uri(baseUri, destination).ToString(); 116 | logger.LogInformation($"{defaultValue} => {result}"); 117 | return result; 118 | } 119 | 120 | /// 121 | /// 处理错误信息 122 | /// 123 | /// 124 | /// 125 | /// 126 | private static async Task HandleErrorAsync(HttpContext context, ForwarderError error) 127 | { 128 | if (error == ForwarderError.None || context.Response.HasStarted) 129 | { 130 | return; 131 | } 132 | 133 | await context.Response.WriteAsJsonAsync(new 134 | { 135 | error = error.ToString(), 136 | message = context.GetForwarderErrorFeature()?.Exception?.Message 137 | }); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/HttpMiddlewares/IRequestLoggingFeature.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.HttpServer.HttpMiddlewares 2 | { 3 | /// 4 | /// 请求日志特性 5 | /// 6 | public interface IRequestLoggingFeature 7 | { 8 | /// 9 | /// 是否启用 10 | /// 11 | bool Enable { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/HttpMiddlewares/RequestLoggingMilldeware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Connections; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Diagnostics; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace FastGithub.HttpServer.HttpMiddlewares 10 | { 11 | /// 12 | /// 请求日志中间件 13 | /// 14 | sealed class RequestLoggingMiddleware 15 | { 16 | private readonly ILogger logger; 17 | 18 | /// 19 | /// 请求日志中间件 20 | /// 21 | /// 22 | public RequestLoggingMiddleware(ILogger logger) 23 | { 24 | this.logger = logger; 25 | } 26 | 27 | /// 28 | /// 执行请求 29 | /// 30 | /// 31 | /// 32 | /// 33 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 34 | { 35 | var feature = new RequestLoggingFeature(); 36 | context.Features.Set(feature); 37 | var stopwatch = Stopwatch.StartNew(); 38 | 39 | try 40 | { 41 | await next(context); 42 | } 43 | finally 44 | { 45 | stopwatch.Stop(); 46 | } 47 | 48 | if (feature.Enable == false) 49 | { 50 | return; 51 | } 52 | 53 | var request = context.Request; 54 | var response = context.Response; 55 | var exception = context.GetForwarderErrorFeature()?.Exception; 56 | if (exception == null) 57 | { 58 | logger.LogInformation($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms"); 59 | } 60 | else if (IsError(exception)) 61 | { 62 | logger.LogError($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms{Environment.NewLine}{exception}"); 63 | } 64 | else 65 | { 66 | logger.LogWarning($"{request.Method} {request.Scheme}://{request.Host}{request.Path} responded {response.StatusCode} in {stopwatch.Elapsed.TotalMilliseconds} ms{Environment.NewLine}{GetMessage(exception)}"); 67 | } 68 | } 69 | 70 | /// 71 | /// 是否为错误 72 | /// 73 | /// 74 | /// 75 | private static bool IsError(Exception exception) 76 | { 77 | if (exception is OperationCanceledException) 78 | { 79 | return false; 80 | } 81 | 82 | if (HasInnerException(exception)) 83 | { 84 | return false; 85 | } 86 | 87 | return true; 88 | } 89 | 90 | /// 91 | /// 是否有内部异常异常 92 | /// 93 | /// 94 | /// 95 | /// 96 | private static bool HasInnerException(Exception exception) where TInnerException : Exception 97 | { 98 | var inner = exception.InnerException; 99 | while (inner != null) 100 | { 101 | if (inner is TInnerException) 102 | { 103 | return true; 104 | } 105 | inner = inner.InnerException; 106 | } 107 | return false; 108 | } 109 | 110 | /// 111 | /// 获取异常信息 112 | /// 113 | /// 114 | /// 115 | private static string GetMessage(Exception exception) 116 | { 117 | var ex = exception; 118 | var builder = new StringBuilder(); 119 | 120 | while (ex != null) 121 | { 122 | var type = ex.GetType(); 123 | builder.Append(type.Namespace).Append('.').Append(type.Name).Append(": ").AppendLine(ex.Message); 124 | ex = ex.InnerException; 125 | } 126 | return builder.ToString(); 127 | } 128 | 129 | private class RequestLoggingFeature : IRequestLoggingFeature 130 | { 131 | public bool Enable { get; set; } = true; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.HttpServer.Certs; 2 | using FastGithub.HttpServer.Certs.CaCertInstallers; 3 | using FastGithub.HttpServer.HttpMiddlewares; 4 | using FastGithub.HttpServer.TcpMiddlewares; 5 | using FastGithub.HttpServer.TlsMiddlewares; 6 | using Microsoft.Extensions.DependencyInjection; 7 | namespace FastGithub 8 | { 9 | /// 10 | /// http反向代理的服务注册扩展 11 | /// 12 | public static class ServiceCollectionExtensions 13 | { 14 | /// 15 | /// 添加http反向代理 16 | /// 17 | /// 18 | /// 19 | public static IServiceCollection AddReverseProxy(this IServiceCollection services) 20 | { 21 | return services 22 | .AddMemoryCache() 23 | .AddHttpForwarder() 24 | .AddSingleton() 25 | .AddSingleton() 26 | .AddSingleton() 27 | .AddSingleton() 28 | .AddSingleton() 29 | 30 | // tcp 31 | .AddSingleton() 32 | .AddSingleton() 33 | 34 | // tls 35 | .AddSingleton() 36 | .AddSingleton() 37 | 38 | // http 39 | .AddSingleton() 40 | .AddSingleton() 41 | .AddSingleton(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TcpMiddlewares/GithubGitReverseProxyHandler.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.DomainResolve; 2 | 3 | namespace FastGithub.HttpServer.TcpMiddlewares 4 | { 5 | /// 6 | /// github的git代理处理者 7 | /// 8 | sealed class GithubGitReverseProxyHandler : TcpReverseProxyHandler 9 | { 10 | /// 11 | /// github的git代理处理者 12 | /// 13 | /// 14 | public GithubGitReverseProxyHandler(IDomainResolver domainResolver) 15 | : base(domainResolver, new("github.com", 9418)) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TcpMiddlewares/GithubSshReverseProxyHandler.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.DomainResolve; 2 | 3 | namespace FastGithub.HttpServer.TcpMiddlewares 4 | { 5 | /// 6 | /// github的ssh代理处理者 7 | /// 8 | sealed class GithubSshReverseProxyHandler : TcpReverseProxyHandler 9 | { 10 | /// 11 | /// github的ssh代理处理者 12 | /// 13 | /// 14 | public GithubSshReverseProxyHandler(IDomainResolver domainResolver) 15 | : base(domainResolver, new("github.com", 22)) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TcpMiddlewares/IHttpProxyFeature.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace FastGithub.HttpServer.TcpMiddlewares 4 | { 5 | interface IHttpProxyFeature 6 | { 7 | HostString ProxyHost { get; } 8 | 9 | ProxyProtocol ProxyProtocol { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TcpMiddlewares/ProxyProtocol.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.HttpServer.TcpMiddlewares 2 | { 3 | /// 4 | /// 代理协议 5 | /// 6 | enum ProxyProtocol 7 | { 8 | /// 9 | /// 无代理 10 | /// 11 | None, 12 | 13 | /// 14 | /// http代理 15 | /// 16 | HttpProxy, 17 | 18 | /// 19 | /// 隧道代理 20 | /// 21 | TunnelProxy 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TcpMiddlewares/TcpReverseProxyHandler.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.DomainResolve; 2 | using Microsoft.AspNetCore.Connections; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.IO.Pipelines; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace FastGithub.HttpServer.TcpMiddlewares 13 | { 14 | /// 15 | /// tcp协议代理处理者 16 | /// 17 | abstract class TcpReverseProxyHandler : ConnectionHandler 18 | { 19 | private readonly IDomainResolver domainResolver; 20 | private readonly DnsEndPoint endPoint; 21 | private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d); 22 | 23 | /// 24 | /// tcp协议代理处理者 25 | /// 26 | /// 27 | /// 28 | public TcpReverseProxyHandler(IDomainResolver domainResolver, DnsEndPoint endPoint) 29 | { 30 | this.domainResolver = domainResolver; 31 | this.endPoint = endPoint; 32 | } 33 | 34 | /// 35 | /// tcp连接后 36 | /// 37 | /// 38 | /// 39 | public override async Task OnConnectedAsync(ConnectionContext context) 40 | { 41 | var cancellationToken = context.ConnectionClosed; 42 | using var connection = await CreateConnectionAsync(cancellationToken); 43 | var task1 = connection.CopyToAsync(context.Transport.Output, cancellationToken); 44 | var task2 = context.Transport.Input.CopyToAsync(connection, cancellationToken); 45 | await Task.WhenAny(task1, task2); 46 | } 47 | 48 | /// 49 | /// 创建连接 50 | /// 51 | /// 52 | /// 53 | /// 54 | private async Task CreateConnectionAsync(CancellationToken cancellationToken) 55 | { 56 | var innerExceptions = new List(); 57 | await foreach (var address in domainResolver.ResolveAsync(endPoint, cancellationToken)) 58 | { 59 | var socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 60 | try 61 | { 62 | using var timeoutTokenSource = new CancellationTokenSource(connectTimeout); 63 | using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token); 64 | await socket.ConnectAsync(address, endPoint.Port, linkedTokenSource.Token); 65 | return new NetworkStream(socket, ownsSocket: false); 66 | } 67 | catch (Exception ex) 68 | { 69 | socket.Dispose(); 70 | cancellationToken.ThrowIfCancellationRequested(); 71 | innerExceptions.Add(ex); 72 | } 73 | } 74 | throw new AggregateException($"无法连接到{endPoint.Host}:{endPoint.Port}", innerExceptions); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TlsMiddlewares/FakeTlsConnectionFeature.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http.Features; 2 | using System; 3 | using System.Security.Cryptography.X509Certificates; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace FastGithub.HttpServer.TlsMiddlewares 8 | { 9 | /// 10 | /// 假冒的TlsConnectionFeature 11 | /// 12 | sealed class FakeTlsConnectionFeature : ITlsConnectionFeature 13 | { 14 | public static FakeTlsConnectionFeature Instance { get; } = new FakeTlsConnectionFeature(); 15 | 16 | public X509Certificate2? ClientCertificate 17 | { 18 | get => throw new NotImplementedException(); 19 | set => throw new NotImplementedException(); 20 | } 21 | 22 | public Task GetClientCertificateAsync(CancellationToken cancellationToken) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TlsMiddlewares/TlsInvadeMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Connections; 2 | using Microsoft.AspNetCore.Http.Features; 3 | using System.Buffers; 4 | using System.IO.Pipelines; 5 | using System.Threading.Tasks; 6 | 7 | namespace FastGithub.HttpServer.TlsMiddlewares 8 | { 9 | /// 10 | /// https入侵中间件 11 | /// 12 | sealed class TlsInvadeMiddleware 13 | { 14 | /// 15 | /// 执行中间件 16 | /// 17 | /// 18 | /// 19 | public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context) 20 | { 21 | // 连接不是tls 22 | if (await IsTlsConnectionAsync(context) == false) 23 | { 24 | // 没有任何tls中间件执行过 25 | if (context.Features.Get() == null) 26 | { 27 | // 设置假的ITlsConnectionFeature,迫使https中间件跳过自身的工作 28 | context.Features.Set(FakeTlsConnectionFeature.Instance); 29 | } 30 | } 31 | await next(context); 32 | } 33 | 34 | 35 | /// 36 | /// 是否为tls协议 37 | /// 38 | /// 39 | /// 40 | private static async Task IsTlsConnectionAsync(ConnectionContext context) 41 | { 42 | try 43 | { 44 | var result = await context.Transport.Input.ReadAtLeastAsync(2, context.ConnectionClosed); 45 | var state = IsTlsProtocol(result); 46 | context.Transport.Input.AdvanceTo(result.Buffer.Start); 47 | return state; 48 | } 49 | catch 50 | { 51 | return false; 52 | } 53 | 54 | static bool IsTlsProtocol(ReadResult result) 55 | { 56 | var reader = new SequenceReader(result.Buffer); 57 | return reader.TryRead(out var firstByte) && 58 | reader.TryRead(out var nextByte) && 59 | firstByte == 0x16 && 60 | nextByte == 0x3; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /FastGithub.HttpServer/TlsMiddlewares/TlsRestoreMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Connections; 2 | using Microsoft.AspNetCore.Http.Features; 3 | using System.Threading.Tasks; 4 | 5 | namespace FastGithub.HttpServer.TlsMiddlewares 6 | { 7 | /// 8 | /// https恢复中间件 9 | /// 10 | sealed class TlsRestoreMiddleware 11 | { 12 | /// 13 | /// 执行中间件 14 | /// 15 | /// 16 | /// 17 | public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context) 18 | { 19 | if (context.Features.Get() == FakeTlsConnectionFeature.Instance) 20 | { 21 | // 擦除入侵 22 | context.Features.Set(null); 23 | } 24 | await next(context); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/Dns/HostsConflictSolver.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.IO; 5 | using System.Runtime.Versioning; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace FastGithub.PacketIntercept.Dns 11 | { 12 | /// 13 | /// host文件冲解决者 14 | /// 15 | [SupportedOSPlatform("windows")] 16 | sealed class HostsConflictSolver : IDnsConflictSolver 17 | { 18 | private readonly FastGithubConfig fastGithubConfig; 19 | private readonly ILogger logger; 20 | 21 | /// 22 | /// host文件冲解决者 23 | /// 24 | /// 25 | /// 26 | public HostsConflictSolver( 27 | FastGithubConfig fastGithubConfig, 28 | ILogger logger) 29 | { 30 | this.fastGithubConfig = fastGithubConfig; 31 | this.logger = logger; 32 | } 33 | 34 | /// 35 | /// 解决冲突 36 | /// 37 | /// 38 | /// 39 | public async Task SolveAsync(CancellationToken cancellationToken) 40 | { 41 | var hostsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers/etc/hosts"); 42 | if (File.Exists(hostsPath) == false) 43 | { 44 | return; 45 | } 46 | 47 | Encoding hostsEncoding; 48 | var hasConflicting = false; 49 | var hostsBuilder = new StringBuilder(); 50 | using (var fileStream = new FileStream(hostsPath, FileMode.Open, FileAccess.Read)) 51 | { 52 | using var streamReader = new StreamReader(fileStream); 53 | while (streamReader.EndOfStream == false) 54 | { 55 | var line = await streamReader.ReadLineAsync(cancellationToken); 56 | if (this.IsConflictingLine(line)) 57 | { 58 | hasConflicting = true; 59 | hostsBuilder.AppendLine($"# {line}"); 60 | } 61 | else 62 | { 63 | hostsBuilder.AppendLine(line); 64 | } 65 | } 66 | hostsEncoding = streamReader.CurrentEncoding; 67 | } 68 | 69 | 70 | if (hasConflicting == true) 71 | { 72 | try 73 | { 74 | await File.WriteAllTextAsync(hostsPath, hostsBuilder.ToString(), hostsEncoding, cancellationToken); 75 | } 76 | catch (Exception ex) 77 | { 78 | this.logger.LogWarning($"无法解决hosts文件冲突:{ex.Message}"); 79 | } 80 | } 81 | } 82 | 83 | /// 84 | /// 恢复冲突 85 | /// 86 | /// 87 | /// 88 | public Task RestoreAsync(CancellationToken cancellationToken) 89 | { 90 | return Task.CompletedTask; 91 | } 92 | 93 | /// 94 | /// 是否为冲突的行 95 | /// 96 | /// 97 | /// 98 | private bool IsConflictingLine(string? line) 99 | { 100 | if (line == null || line.TrimStart().StartsWith("#")) 101 | { 102 | return false; 103 | } 104 | 105 | var items = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); 106 | if (items.Length < 2) 107 | { 108 | return false; 109 | } 110 | 111 | var domain = items[1]; 112 | return this.fastGithubConfig.IsMatch(domain); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/DnsInterceptHostedService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Runtime.Versioning; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace FastGithub.PacketIntercept 11 | { 12 | /// 13 | /// dns拦截后台服务 14 | /// 15 | [SupportedOSPlatform("windows")] 16 | sealed class DnsInterceptHostedService : BackgroundService 17 | { 18 | private readonly IDnsInterceptor dnsInterceptor; 19 | private readonly IEnumerable conflictSolvers; 20 | private readonly ILogger logger; 21 | private readonly IHost host; 22 | 23 | /// 24 | /// dns拦截后台服务 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | public DnsInterceptHostedService( 31 | IDnsInterceptor dnsInterceptor, 32 | IEnumerable conflictSolvers, 33 | ILogger logger, 34 | IHost host) 35 | { 36 | this.dnsInterceptor = dnsInterceptor; 37 | this.conflictSolvers = conflictSolvers; 38 | this.logger = logger; 39 | this.host = host; 40 | } 41 | 42 | /// 43 | /// 启动时处理冲突 44 | /// 45 | /// 46 | /// 47 | public override async Task StartAsync(CancellationToken cancellationToken) 48 | { 49 | foreach (var solver in this.conflictSolvers) 50 | { 51 | await solver.SolveAsync(cancellationToken); 52 | } 53 | await base.StartAsync(cancellationToken); 54 | } 55 | 56 | /// 57 | /// 停止时恢复冲突 58 | /// 59 | /// 60 | /// 61 | public override async Task StopAsync(CancellationToken cancellationToken) 62 | { 63 | foreach (var solver in this.conflictSolvers) 64 | { 65 | await solver.RestoreAsync(cancellationToken); 66 | } 67 | await base.StopAsync(cancellationToken); 68 | } 69 | 70 | /// 71 | /// dns后台 72 | /// 73 | /// 74 | /// 75 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 76 | { 77 | try 78 | { 79 | await this.dnsInterceptor.InterceptAsync(stoppingToken); 80 | } 81 | catch (OperationCanceledException) 82 | { 83 | } 84 | catch (Win32Exception ex) when (ex.NativeErrorCode == 995) 85 | { 86 | } 87 | catch (Exception ex) 88 | { 89 | this.logger.LogError(ex, "dns拦截器异常"); 90 | await this.host.StopAsync(stoppingToken); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/FastGithub.PacketIntercept.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/IDnsConflictSolver.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace FastGithub.PacketIntercept 5 | { 6 | /// 7 | /// Dns冲突解决者 8 | /// 9 | interface IDnsConflictSolver 10 | { 11 | /// 12 | /// 解决冲突 13 | /// 14 | /// 15 | /// 16 | Task SolveAsync(CancellationToken cancellationToken); 17 | 18 | /// 19 | /// 恢复冲突 20 | /// 21 | /// 22 | /// 23 | Task RestoreAsync(CancellationToken cancellationToken); 24 | } 25 | } -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/IDnsInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace FastGithub.PacketIntercept 5 | { 6 | /// 7 | /// dns拦截器接口 8 | /// 9 | interface IDnsInterceptor 10 | { 11 | /// 12 | /// 拦截数据包 13 | /// 14 | /// 15 | /// 16 | Task InterceptAsync(CancellationToken cancellationToken); 17 | } 18 | } -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/ITcpInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace FastGithub.PacketIntercept 5 | { 6 | /// 7 | /// tcp拦截器接口 8 | /// 9 | interface ITcpInterceptor 10 | { 11 | /// 12 | /// 拦截数据包 13 | /// 14 | /// 15 | /// 16 | Task InterceptAsync(CancellationToken cancellationToken); 17 | } 18 | } -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.PacketIntercept; 2 | using FastGithub.PacketIntercept.Dns; 3 | using FastGithub.PacketIntercept.Tcp; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.DependencyInjection.Extensions; 6 | using System.Runtime.Versioning; 7 | 8 | namespace FastGithub 9 | { 10 | /// 11 | /// 服务注册扩展 12 | /// 13 | public static class ServiceCollectionExtensions 14 | { 15 | /// 16 | /// 注册数据包拦截器 17 | /// 18 | /// 19 | /// 20 | [SupportedOSPlatform("windows")] 21 | public static IServiceCollection AddPacketIntercept(this IServiceCollection services) 22 | { 23 | services.AddSingleton(); 24 | services.AddSingleton(); 25 | services.TryAddSingleton(); 26 | services.AddHostedService(); 27 | 28 | services.AddSingleton(); 29 | services.AddSingleton(); 30 | services.AddSingleton(); 31 | services.AddSingleton(); 32 | services.AddHostedService(); 33 | 34 | return services; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/Tcp/GitInterceptor.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using System.Runtime.Versioning; 4 | 5 | namespace FastGithub.PacketIntercept.Tcp 6 | { 7 | /// 8 | /// git拦截器 9 | /// 10 | [SupportedOSPlatform("windows")] 11 | sealed class GitInterceptor : TcpInterceptor 12 | { 13 | /// 14 | /// git拦截器 15 | /// 16 | /// 17 | public GitInterceptor(ILogger logger) 18 | : base(9418, GlobalListener.GitPort, logger) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/Tcp/HttpInterceptor.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using System.Runtime.Versioning; 4 | 5 | namespace FastGithub.PacketIntercept.Tcp 6 | { 7 | /// 8 | /// http拦截器 9 | /// 10 | [SupportedOSPlatform("windows")] 11 | sealed class HttpInterceptor : TcpInterceptor 12 | { 13 | /// 14 | /// http拦截器 15 | /// 16 | /// 17 | public HttpInterceptor(ILogger logger) 18 | : base(80, GlobalListener.HttpPort, logger) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/Tcp/HttpsInterceptor.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using System.Runtime.Versioning; 4 | 5 | namespace FastGithub.PacketIntercept.Tcp 6 | { 7 | /// 8 | /// https拦截器 9 | /// 10 | [SupportedOSPlatform("windows")] 11 | sealed class HttpsInterceptor : TcpInterceptor 12 | { 13 | /// 14 | /// https拦截器 15 | /// 16 | /// 17 | public HttpsInterceptor(ILogger logger) 18 | : base(443, GlobalListener.HttpsPort, logger) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/Tcp/SshInterceptor.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using System.Runtime.Versioning; 4 | 5 | namespace FastGithub.PacketIntercept.Tcp 6 | { 7 | /// 8 | /// ssh拦截器 9 | /// 10 | [SupportedOSPlatform("windows")] 11 | sealed class SshInterceptor : TcpInterceptor 12 | { 13 | /// 14 | /// ssh拦截器 15 | /// 16 | /// 17 | public SshInterceptor(ILogger logger) 18 | : base(22, GlobalListener.SshPort, logger) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/Tcp/TcpInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Runtime.Versioning; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using WindivertDotnet; 10 | 11 | namespace FastGithub.PacketIntercept.Tcp 12 | { 13 | /// 14 | /// tcp拦截器 15 | /// 16 | [SupportedOSPlatform("windows")] 17 | abstract class TcpInterceptor : ITcpInterceptor 18 | { 19 | private readonly Filter filter; 20 | private readonly ushort oldServerPort; 21 | private readonly ushort newServerPort; 22 | private readonly ILogger logger; 23 | 24 | /// 25 | /// tcp拦截器 26 | /// 27 | /// 修改前的服务器端口 28 | /// 修改后的服务器端口 29 | /// 30 | public TcpInterceptor(int oldServerPort, int newServerPort, ILogger logger) 31 | { 32 | this.filter = Filter.True 33 | .And(f => f.Network.Loopback) 34 | .And(f => f.Tcp.DstPort == oldServerPort || f.Tcp.SrcPort == newServerPort); 35 | 36 | this.oldServerPort = (ushort)oldServerPort; 37 | this.newServerPort = (ushort)newServerPort; 38 | this.logger = logger; 39 | } 40 | 41 | /// 42 | /// 拦截指定端口的数据包 43 | /// 44 | /// 45 | /// 46 | public async Task InterceptAsync(CancellationToken cancellationToken) 47 | { 48 | if (this.oldServerPort == this.newServerPort) 49 | { 50 | return; 51 | } 52 | 53 | using var divert = new WinDivert(this.filter, WinDivertLayer.Network); 54 | using var packet = new WinDivertPacket(); 55 | using var addr = new WinDivertAddress(); 56 | 57 | if (Socket.OSSupportsIPv4) 58 | { 59 | this.logger.LogInformation($"{IPAddress.Loopback}:{this.oldServerPort} <=> {IPAddress.Loopback}:{this.newServerPort}"); 60 | } 61 | if (Socket.OSSupportsIPv6) 62 | { 63 | this.logger.LogInformation($"{IPAddress.IPv6Loopback}:{this.oldServerPort} <=> {IPAddress.IPv6Loopback}:{this.newServerPort}"); 64 | } 65 | 66 | while (cancellationToken.IsCancellationRequested == false) 67 | { 68 | await divert.RecvAsync(packet, addr, cancellationToken); 69 | try 70 | { 71 | this.ModifyTcpPacket(packet, addr); 72 | } 73 | catch (Exception ex) 74 | { 75 | this.logger.LogWarning(ex.Message); 76 | } 77 | finally 78 | { 79 | await divert.SendAsync(packet, addr, cancellationToken); 80 | } 81 | } 82 | } 83 | 84 | /// 85 | /// 修改tcp数据端口的端口 86 | /// 87 | /// 88 | /// 89 | unsafe private void ModifyTcpPacket(WinDivertPacket packet, WinDivertAddress addr) 90 | { 91 | var result = packet.GetParseResult(); 92 | if (result.TcpHeader->DstPort == oldServerPort) 93 | { 94 | result.TcpHeader->DstPort = this.newServerPort; 95 | } 96 | else 97 | { 98 | result.TcpHeader->SrcPort = oldServerPort; 99 | } 100 | addr.Flags |= WinDivertAddressFlag.Impostor; 101 | packet.CalcChecksums(addr); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /FastGithub.PacketIntercept/TcpInterceptHostedService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Runtime.Versioning; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace FastGithub.PacketIntercept 12 | { 13 | /// 14 | /// tcp拦截后台服务 15 | /// 16 | [SupportedOSPlatform("windows")] 17 | sealed class TcpInterceptHostedService : BackgroundService 18 | { 19 | private readonly IEnumerable tcpInterceptors; 20 | private readonly ILogger logger; 21 | private readonly IHost host; 22 | 23 | /// 24 | /// tcp拦截后台服务 25 | /// 26 | /// 27 | /// 28 | /// 29 | public TcpInterceptHostedService( 30 | IEnumerable tcpInterceptors, 31 | ILogger logger, 32 | IHost host) 33 | { 34 | this.tcpInterceptors = tcpInterceptors; 35 | this.logger = logger; 36 | this.host = host; 37 | } 38 | 39 | /// 40 | /// https后台 41 | /// 42 | /// 43 | /// 44 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 45 | { 46 | try 47 | { 48 | var tasks = this.tcpInterceptors.Select(item => item.InterceptAsync(stoppingToken)); 49 | await Task.WhenAll(tasks); 50 | } 51 | catch (OperationCanceledException) 52 | { 53 | } 54 | catch (Win32Exception ex) when (ex.NativeErrorCode == 995) 55 | { 56 | } 57 | catch (Exception ex) 58 | { 59 | this.logger.LogError(ex, "tcp拦截器异常"); 60 | await this.host.StopAsync(stoppingToken); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /FastGithub.UI/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /FastGithub.UI/FastGithub.UI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | true 6 | WinExe 7 | 8.0 8 | net45 9 | app.ico 10 | app.manifest 11 | MIT 12 | False 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | all 28 | 29 | 30 | all 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /FastGithub.UI/FlowChart.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 上行流量 33 | 34 | 35 | 下行流量 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /FastGithub.UI/FlowChart.xaml.cs: -------------------------------------------------------------------------------- 1 | using LiveCharts; 2 | using LiveCharts.Configurations; 3 | using LiveCharts.Wpf; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | using System.Windows.Controls; 9 | 10 | namespace FastGithub.UI 11 | { 12 | /// 13 | /// FlowChart.xaml 的交互逻辑 14 | /// 15 | public partial class FlowChart : UserControl 16 | { 17 | private readonly LineSeries readSeries = new LineSeries 18 | { 19 | Title = "上行速率", 20 | PointGeometry = null, 21 | LineSmoothness = 1D, 22 | Values = new ChartValues() 23 | }; 24 | 25 | private readonly LineSeries writeSeries = new LineSeries() 26 | { 27 | Title = "下行速率", 28 | PointGeometry = null, 29 | LineSmoothness = 1D, 30 | Values = new ChartValues() 31 | }; 32 | 33 | private static DateTime GetDateTime(double timestamp) => new DateTime(1970, 1, 1).Add(TimeSpan.FromMilliseconds(timestamp)).ToLocalTime(); 34 | 35 | private static double GetTimestamp(DateTime dateTime) => dateTime.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; 36 | 37 | 38 | public SeriesCollection Series { get; } = new SeriesCollection(Mappers.Xy().X(item => item.Timestamp).Y(item => item.Rate)); 39 | 40 | public Func XFormatter { get; } = timestamp => GetDateTime(timestamp).ToString("HH:mm:ss"); 41 | 42 | public Func YFormatter { get; } = value => $"{FlowStatistics.ToNetworkSizeString((long)value)}/s"; 43 | 44 | public FlowChart() 45 | { 46 | InitializeComponent(); 47 | 48 | this.Series.Add(this.readSeries); 49 | this.Series.Add(this.writeSeries); 50 | 51 | this.DataContext = this; 52 | this.InitFlowChartAsync(); 53 | } 54 | 55 | private async void InitFlowChartAsync() 56 | { 57 | using var httpClient = new HttpClient(); 58 | while (this.Dispatcher.HasShutdownStarted == false) 59 | { 60 | try 61 | { 62 | await this.FlushFlowStatisticsAsync(httpClient); 63 | } 64 | catch (Exception) 65 | { 66 | } 67 | finally 68 | { 69 | await Task.Delay(TimeSpan.FromSeconds(1d)); 70 | } 71 | } 72 | } 73 | 74 | private async Task FlushFlowStatisticsAsync(HttpClient httpClient) 75 | { 76 | var response = await httpClient.GetAsync("http://localhost/flowStatistics"); 77 | var json = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); 78 | var flowStatistics = JsonConvert.DeserializeObject(json); 79 | if (flowStatistics == null) 80 | { 81 | return; 82 | } 83 | 84 | this.textBlockRead.Text = FlowStatistics.ToNetworkSizeString(flowStatistics.TotalRead); 85 | this.textBlockWrite.Text = FlowStatistics.ToNetworkSizeString(flowStatistics.TotalWrite); 86 | 87 | var timestamp = GetTimestamp(DateTime.Now); 88 | this.readSeries.Values.Add(new RateTick(flowStatistics.ReadRate, timestamp)); 89 | this.writeSeries.Values.Add(new RateTick(flowStatistics.WriteRate, timestamp)); 90 | 91 | if (this.readSeries.Values.Count > 60) 92 | { 93 | this.readSeries.Values.RemoveAt(0); 94 | this.writeSeries.Values.RemoveAt(0); 95 | } 96 | } 97 | 98 | private class RateTick 99 | { 100 | public double Rate { get; } 101 | 102 | public double Timestamp { get; } 103 | 104 | public RateTick(double rate, double timestamp) 105 | { 106 | this.Rate = rate; 107 | this.Timestamp = timestamp; 108 | } 109 | } 110 | 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /FastGithub.UI/FlowStatistics.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.UI 2 | { 3 | /// 4 | /// 流量统计 5 | /// 6 | public class FlowStatistics 7 | { 8 | /// 9 | /// 获取总读上行 10 | /// 11 | public long TotalRead { get; set; } 12 | 13 | /// 14 | /// 获取总下行 15 | /// 16 | public long TotalWrite { get; set; } 17 | 18 | /// 19 | /// 获取上行速率 20 | /// 21 | public double ReadRate { get; set; } 22 | 23 | /// 24 | /// 获取下行速率 25 | /// 26 | public double WriteRate { get; set; } 27 | 28 | 29 | public static string ToNetworkSizeString(long value) 30 | { 31 | if (value < 1024) 32 | { 33 | return $"{value}B"; 34 | } 35 | if (value < 1024 * 1024) 36 | { 37 | return $"{value / 1024d:0.00}KB"; 38 | } 39 | return $"{value / 1024d / 1024d:0.00}MB"; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /FastGithub.UI/IssuesWebbrowser.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /FastGithub.UI/IssuesWebbrowser.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Input; 5 | 6 | namespace FastGithub.UI 7 | { 8 | /// 9 | /// IssuesWebbrowser.xaml 的交互逻辑 10 | /// 11 | public partial class IssuesWebbrowser : UserControl 12 | { 13 | public IssuesWebbrowser() 14 | { 15 | InitializeComponent(); 16 | 17 | this.NavigateIssueHtml(); 18 | this.webBrowser.AddHandler(KeyDownEvent, new RoutedEventHandler(WebBrowser_KeyDown), true); 19 | } 20 | 21 | /// 22 | /// 拦截F5 23 | /// 24 | /// 25 | /// 26 | private void WebBrowser_KeyDown(object sender, RoutedEventArgs e) 27 | { 28 | var @event = (KeyEventArgs)e; 29 | if (@event.Key == Key.F5) 30 | { 31 | this.NavigateIssueHtml(); 32 | } 33 | } 34 | 35 | private void NavigateIssueHtml() 36 | { 37 | try 38 | { 39 | var resource = Application.GetResourceStream(new Uri("Resource/issue.html", UriKind.Relative)); 40 | this.webBrowser.NavigateToStream(resource.Stream); 41 | } 42 | catch (Exception) 43 | { 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FastGithub.UI/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub.UI 2 | { 3 | /// 4 | /// 日志等级 5 | /// 6 | public enum LogLevel 7 | { 8 | Verbose, 9 | Debug, 10 | Information, 11 | Warning, 12 | Error, 13 | Fatal 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FastGithub.UI/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Windows; 5 | using System.Windows.Interop; 6 | 7 | namespace FastGithub.UI 8 | { 9 | /// 10 | /// MainWindow.xaml 的交互逻辑 11 | /// 12 | public partial class MainWindow : Window 13 | { 14 | private readonly System.Windows.Forms.NotifyIcon notifyIcon; 15 | private const string FASTGITHUB_UI = "FastGithub.UI"; 16 | private const string RELEASES_URI = "https://github.com/creazyboyone/FastGithub"; 17 | 18 | public MainWindow() 19 | { 20 | InitializeComponent(); 21 | 22 | var upgrade = new System.Windows.Forms.MenuItem("检测更新(&U)"); 23 | upgrade.Click += (s, e) => Process.Start(RELEASES_URI); 24 | 25 | var exit = new System.Windows.Forms.MenuItem("关闭应用(&C)"); 26 | exit.Click += (s, e) => this.Close(); 27 | 28 | var version = this.GetType().Assembly.GetCustomAttribute()?.InformationalVersion; 29 | this.Title = $"{FASTGITHUB_UI} v{version}"; 30 | this.notifyIcon = new System.Windows.Forms.NotifyIcon 31 | { 32 | Visible = true, 33 | Text = FASTGITHUB_UI, 34 | ContextMenu = new System.Windows.Forms.ContextMenu(new[] { upgrade, exit }), 35 | Icon = System.Drawing.Icon.ExtractAssociatedIcon(System.Windows.Forms.Application.ExecutablePath) 36 | }; 37 | 38 | this.notifyIcon.MouseClick += (s, e) => 39 | { 40 | if (e.Button == System.Windows.Forms.MouseButtons.Left) 41 | { 42 | this.Show(); 43 | this.Activate(); 44 | this.WindowState = WindowState.Normal; 45 | } 46 | }; 47 | } 48 | 49 | 50 | /// 51 | /// 拦截最小化事件 52 | /// 53 | /// 54 | protected override void OnSourceInitialized(EventArgs e) 55 | { 56 | base.OnSourceInitialized(e); 57 | var hwndSource = (HwndSource)PresentationSource.FromVisual(this); 58 | hwndSource.AddHook(WndProc); 59 | 60 | IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 61 | { 62 | const int WM_SYSCOMMAND = 0x112; 63 | const int SC_MINIMIZE = 0xf020; 64 | const int SC_CLOSE = 0xf060; 65 | 66 | if (msg == WM_SYSCOMMAND) 67 | { 68 | if (wParam.ToInt32() == SC_MINIMIZE || wParam.ToInt32() == SC_CLOSE) 69 | { 70 | this.Hide(); 71 | handled = true; 72 | } 73 | } 74 | return IntPtr.Zero; 75 | } 76 | } 77 | 78 | /// 79 | /// 关闭时 80 | /// 81 | /// 82 | protected override void OnClosed(EventArgs e) 83 | { 84 | this.notifyIcon.Icon = null; 85 | this.notifyIcon.Dispose(); 86 | base.OnClosed(e); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /FastGithub.UI/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Windows; 8 | 9 | namespace FastGithub.UI 10 | { 11 | class Program 12 | { 13 | private const string MUTEX_NAME = "Global\\FastGithub.UI"; 14 | private const string MAIN_WINDOWS = "MainWindow.xaml"; 15 | private const string FASTGITHUB_PATH = "fastgithub.exe"; 16 | 17 | [STAThread] 18 | static void Main(string[] args) 19 | { 20 | AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; 21 | using var mutex = new Mutex(true, MUTEX_NAME, out var isFirstInstance); 22 | if (isFirstInstance == false) 23 | { 24 | return; 25 | } 26 | 27 | StartFastGithub(); 28 | SetWebBrowserDPI(); 29 | SetWebBrowserVersion(); 30 | 31 | var app = new Application(); 32 | app.StartupUri = new Uri(MAIN_WINDOWS, UriKind.Relative); 33 | app.Run(); 34 | } 35 | 36 | /// 37 | /// 程序集加载失败时 38 | /// 39 | /// 40 | /// 41 | /// 42 | private static Assembly? OnAssemblyResolve(object sender, ResolveEventArgs args) 43 | { 44 | var name = new AssemblyName(args.Name).Name; 45 | if (name.EndsWith(".resources")) 46 | { 47 | return default; 48 | } 49 | 50 | var stream = Application.GetResourceStream(new Uri($"Resource/{name}.dll", UriKind.Relative)).Stream; 51 | var buffer = new byte[stream.Length]; 52 | stream.Read(buffer, 0, buffer.Length); 53 | return Assembly.Load(buffer); 54 | } 55 | 56 | /// 57 | /// 设置浏览器版本 58 | /// 59 | private static void SetWebBrowserVersion() 60 | { 61 | const string subKey = @"Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION"; 62 | var registryKey = Registry.CurrentUser.OpenSubKey(subKey, true); 63 | if (registryKey == null) 64 | { 65 | registryKey = Registry.CurrentUser.CreateSubKey(subKey); 66 | } 67 | var name = $"{Process.GetCurrentProcess().ProcessName}.exe"; 68 | using var webBrowser = new System.Windows.Forms.WebBrowser(); 69 | var value = int.Parse($"{webBrowser.Version.Major}000"); 70 | registryKey.SetValue(name, value, RegistryValueKind.DWord); 71 | } 72 | 73 | /// 74 | /// 设置浏览器DPI 75 | /// 76 | private static void SetWebBrowserDPI() 77 | { 78 | const string subKey = @"Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_96DPI_PIXEL"; 79 | var registryKey = Registry.CurrentUser.OpenSubKey(subKey, true); 80 | if (registryKey == null) 81 | { 82 | registryKey = Registry.CurrentUser.CreateSubKey(subKey); 83 | } 84 | var name = $"{Process.GetCurrentProcess().ProcessName}.exe"; 85 | registryKey.SetValue(name, 1, RegistryValueKind.DWord); 86 | } 87 | 88 | /// 89 | /// 启动fastgithub 90 | /// 91 | /// 92 | private static void StartFastGithub() 93 | { 94 | if (File.Exists(FASTGITHUB_PATH) == false) 95 | { 96 | return; 97 | } 98 | 99 | var startInfo = new ProcessStartInfo 100 | { 101 | FileName = FASTGITHUB_PATH, 102 | Arguments = $"ParentProcessId={Process.GetCurrentProcess().Id} UdpLoggerPort={UdpLogger.Port}", 103 | UseShellExecute = false, 104 | CreateNoWindow = true 105 | }; 106 | Process.Start(startInfo); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /FastGithub.UI/Resource/LiveCharts.Wpf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/FastGithub.UI/Resource/LiveCharts.Wpf.dll -------------------------------------------------------------------------------- /FastGithub.UI/Resource/LiveCharts.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/FastGithub.UI/Resource/LiveCharts.dll -------------------------------------------------------------------------------- /FastGithub.UI/Resource/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/FastGithub.UI/Resource/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /FastGithub.UI/Resource/issue.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 证书验证 9 | 29 | 30 | 31 | 32 |
33 |

Firefox

34 |
35 |

建立安全连接失败

36 |

连接到 github.com 时发生错误。对等端的证书有一个无效的签名。

37 |

错误代码:SEC_ERROR_BAD_SIGNATURE

38 |
39 | 40 |

解决办法

41 |

42 | 1 43 | 地址栏输入:about:config 44 |

45 |

46 | 2 47 | 输入首选项名称:security.enterprise_roots.enabled 48 |

49 |

50 | 3 51 | 修改值为:true 52 |

53 |
54 | 55 |
56 | 57 |
58 |

git.exe

59 |
60 |

clone、pull或push等证书异常

61 |

fatal: unable to access 'https://github.com/xxx.git/'

62 |

SSL certificate problem: unable to get local issuer certificate

63 |
64 | 65 |

解决办法

66 |

67 | 1 68 | 管理员身份运行:cmd 69 |

70 |

71 | 2 72 | 在cmd输入:git config --global http.sslverify false 73 |

74 |
75 | 76 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /FastGithub.UI/UdpLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.NetworkInformation; 5 | using System.Net.Sockets; 6 | using System.Windows; 7 | 8 | namespace FastGithub.UI 9 | { 10 | public class UdpLog 11 | { 12 | public DateTime Timestamp { get; set; } 13 | 14 | public LogLevel Level { get; set; } 15 | 16 | public string Message { get; set; } = string.Empty; 17 | 18 | public string SourceContext { get; set; } = string.Empty; 19 | 20 | public string Color => this.Level <= LogLevel.Information ? "#333" : "IndianRed"; 21 | 22 | /// 23 | /// 复制到剪贴板 24 | /// 25 | public void SetToClipboard() 26 | { 27 | Clipboard.SetText($"{this.Timestamp:yyyy-MM-dd HH:mm:ss.fff}\r\n{this.Message}"); 28 | } 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /FastGithub.UI/UdpLogListBox.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /FastGithub.UI/UdpLogListBox.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Windows.Controls; 4 | 5 | namespace FastGithub.UI 6 | { 7 | /// 8 | /// UdpLogListBox.xaml 的交互逻辑 9 | /// 10 | public partial class UdpLogListBox : UserControl 11 | { 12 | private readonly int maxLogCount = 100; 13 | public ObservableCollection LogList { get; } = new ObservableCollection(); 14 | 15 | public UdpLogListBox() 16 | { 17 | InitializeComponent(); 18 | 19 | this.DataContext = this; 20 | this.InitUdpLoggerAsync(); 21 | } 22 | 23 | private async void InitUdpLoggerAsync() 24 | { 25 | while (this.Dispatcher.HasShutdownStarted == false) 26 | { 27 | try 28 | { 29 | var log = await UdpLogger.GetUdpLogAsync(); 30 | if (log != null) 31 | { 32 | this.LogList.Insert(0, log); 33 | if (this.LogList.Count > this.maxLogCount) 34 | { 35 | this.LogList.RemoveAt(this.maxLogCount); 36 | } 37 | } 38 | } 39 | catch (Exception) 40 | { 41 | } 42 | } 43 | } 44 | 45 | private void MenuItem_Copy_Click(object sender, System.Windows.RoutedEventArgs e) 46 | { 47 | if (this.listBox.SelectedValue is UdpLog udpLog) 48 | { 49 | udpLog.SetToClipboard(); 50 | } 51 | } 52 | 53 | private void MenuItem_Clear_Click(object sender, System.Windows.RoutedEventArgs e) 54 | { 55 | this.LogList.Clear(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /FastGithub.UI/UdpLogger.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Net.NetworkInformation; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace FastGithub.UI 11 | { 12 | static class UdpLogger 13 | { 14 | private static readonly byte[] buffer = new byte[ushort.MaxValue]; 15 | private static readonly Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp); 16 | 17 | /// 18 | /// 获取日志端口 19 | /// 20 | public static int Port { get; } = GetAvailableUdpPort(38457); 21 | 22 | 23 | static UdpLogger() 24 | { 25 | socket.Bind(new IPEndPoint(IPAddress.Loopback, Port)); 26 | } 27 | 28 | /// 29 | /// 获取可用的随机Udp端口 30 | /// 31 | /// 32 | /// 33 | /// 34 | private static int GetAvailableUdpPort(int minValue, AddressFamily addressFamily = AddressFamily.InterNetwork) 35 | { 36 | var hashSet = new HashSet(); 37 | var tcpListeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners(); 38 | 39 | foreach (var endpoint in tcpListeners) 40 | { 41 | if (endpoint.AddressFamily == addressFamily) 42 | { 43 | hashSet.Add(endpoint.Port); 44 | } 45 | } 46 | 47 | for (var port = minValue; port < IPEndPoint.MaxPort; port++) 48 | { 49 | if (hashSet.Contains(port) == false) 50 | { 51 | return port; 52 | } 53 | } 54 | 55 | throw new ArgumentException("当前无可用的端口"); 56 | } 57 | 58 | public static async Task GetUdpLogAsync() 59 | { 60 | EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); 61 | var taskCompletionSource = new TaskCompletionSource(); 62 | socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEP, EndReceiveFrom, taskCompletionSource); 63 | var length = await taskCompletionSource.Task; 64 | 65 | var json = Encoding.UTF8.GetString(buffer, 0, length); 66 | var log = JsonConvert.DeserializeObject(json); 67 | if (log != null) 68 | { 69 | log.Message = log.Message.Replace("\"", null); 70 | } 71 | return log; 72 | } 73 | 74 | private static void EndReceiveFrom(IAsyncResult ar) 75 | { 76 | EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); 77 | var length = socket.EndReceiveFrom(ar, ref remoteEP); 78 | var taskCompletionSource = (TaskCompletionSource)ar.AsyncState; 79 | taskCompletionSource.TrySetResult(length); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /FastGithub.UI/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/FastGithub.UI/app.ico -------------------------------------------------------------------------------- /FastGithub.UI/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 61 | 62 | 63 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /FastGithub.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32203.90 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub", "FastGithub\FastGithub.csproj", "{C1099390-6103-4917-A740-A3002B542FE0}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.Http", "FastGithub.Http\FastGithub.Http.csproj", "{B5DCB3E4-5094-4170-B844-6F395002CA42}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.DomainResolve", "FastGithub.DomainResolve\FastGithub.DomainResolve.csproj", "{5D26ABDD-F341-4EB7-9D08-FCB80F79B4B4}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.Configuration", "FastGithub.Configuration\FastGithub.Configuration.csproj", "{C63CEBB1-56DA-4AC3-BDC9-52424EC292A0}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.HttpServer", "FastGithub.HttpServer\FastGithub.HttpServer.csproj", "{C9807DA0-4620-445E-ABBF-57A617B8E773}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.PacketIntercept", "FastGithub.PacketIntercept\FastGithub.PacketIntercept.csproj", "{701FF90C-E651-4E0B-AE7F-84D1F17DD178}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.UI", "FastGithub.UI\FastGithub.UI.csproj", "{5082061F-38D5-4F50-945E-791C85B9BDB5}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.FlowAnalyze", "FastGithub.FlowAnalyze\FastGithub.FlowAnalyze.csproj", "{93478EAF-739C-47DA-B8FE-AEBA78A75E11}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {C1099390-6103-4917-A740-A3002B542FE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {C1099390-6103-4917-A740-A3002B542FE0}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {C1099390-6103-4917-A740-A3002B542FE0}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {C1099390-6103-4917-A740-A3002B542FE0}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {B5DCB3E4-5094-4170-B844-6F395002CA42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {B5DCB3E4-5094-4170-B844-6F395002CA42}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {B5DCB3E4-5094-4170-B844-6F395002CA42}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {B5DCB3E4-5094-4170-B844-6F395002CA42}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {5D26ABDD-F341-4EB7-9D08-FCB80F79B4B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {5D26ABDD-F341-4EB7-9D08-FCB80F79B4B4}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {5D26ABDD-F341-4EB7-9D08-FCB80F79B4B4}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {5D26ABDD-F341-4EB7-9D08-FCB80F79B4B4}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {C63CEBB1-56DA-4AC3-BDC9-52424EC292A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {C63CEBB1-56DA-4AC3-BDC9-52424EC292A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {C63CEBB1-56DA-4AC3-BDC9-52424EC292A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {C63CEBB1-56DA-4AC3-BDC9-52424EC292A0}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {C9807DA0-4620-445E-ABBF-57A617B8E773}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {C9807DA0-4620-445E-ABBF-57A617B8E773}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {C9807DA0-4620-445E-ABBF-57A617B8E773}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {C9807DA0-4620-445E-ABBF-57A617B8E773}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {701FF90C-E651-4E0B-AE7F-84D1F17DD178}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {701FF90C-E651-4E0B-AE7F-84D1F17DD178}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {701FF90C-E651-4E0B-AE7F-84D1F17DD178}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {701FF90C-E651-4E0B-AE7F-84D1F17DD178}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {5082061F-38D5-4F50-945E-791C85B9BDB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {5082061F-38D5-4F50-945E-791C85B9BDB5}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {5082061F-38D5-4F50-945E-791C85B9BDB5}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {5082061F-38D5-4F50-945E-791C85B9BDB5}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {AC66D417-C355-4DFD-BC3C-DD0383F855D3} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /FastGithub/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "5.0.8", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /FastGithub/AppOptions.cs: -------------------------------------------------------------------------------- 1 | namespace FastGithub 2 | { 3 | /// 4 | /// app选项 5 | /// 6 | public record AppOptions 7 | { 8 | /// 9 | /// 父进程id 10 | /// 11 | public int ParentProcessId { get; init; } 12 | 13 | /// 14 | /// udp日志服务器端口 15 | /// 16 | public int UdpLoggerPort { get; init; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FastGithub/ConsoleUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Runtime.Versioning; 4 | 5 | namespace FastGithub 6 | { 7 | static class ConsoleUtil 8 | { 9 | private const uint ENABLE_QUICK_EDIT = 0x0040; 10 | 11 | private const int STD_INPUT_HANDLE = -10; 12 | 13 | [DllImport("kernel32.dll", SetLastError = true)] 14 | [SupportedOSPlatform("windows")] 15 | private static extern IntPtr GetStdHandle(int nStdHandle); 16 | 17 | [DllImport("kernel32.dll", SetLastError = true)] 18 | [SupportedOSPlatform("windows")] 19 | private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); 20 | 21 | [DllImport("kernel32.dll", SetLastError = true)] 22 | [SupportedOSPlatform("windows")] 23 | private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); 24 | 25 | /// 26 | /// 禁用快速编辑模式 27 | /// 28 | /// 29 | public static bool DisableQuickEdit() 30 | { 31 | if (OperatingSystem.IsWindows()) 32 | { 33 | var hwnd = GetStdHandle(STD_INPUT_HANDLE); 34 | if (GetConsoleMode(hwnd, out uint mode)) 35 | { 36 | mode &= ~ENABLE_QUICK_EDIT; 37 | return SetConsoleMode(hwnd, mode); 38 | } 39 | } 40 | 41 | return false; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FastGithub/FastGithub.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | fastgithub 5 | Exe 6 | MIT 7 | app.manifest 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | PreserveNewest 36 | 37 | 38 | 39 | 40 | 41 | PreserveNewest 42 | 43 | 44 | PreserveNewest 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /FastGithub/ProductionVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Text.RegularExpressions; 4 | using WindivertDotnet; 5 | 6 | namespace FastGithub 7 | { 8 | /// 9 | /// 表示产品版本 10 | /// 11 | public class ProductionVersion : IComparable 12 | { 13 | private static readonly string? productionVersion = Assembly 14 | .GetEntryAssembly()? 15 | .GetCustomAttribute()? 16 | .InformationalVersion; 17 | 18 | /// 19 | /// 获取当前应用程序的产品版本 20 | /// 21 | public static ProductionVersion? Current { get; } = productionVersion == null ? null : Parse(productionVersion); 22 | 23 | 24 | /// 25 | /// 版本 26 | /// 27 | public Version Version { get; } 28 | 29 | /// 30 | /// 子版本 31 | /// 32 | public string SubVersion { get; } 33 | 34 | /// 35 | /// 产品版本 36 | /// 37 | /// 38 | /// 39 | public ProductionVersion(Version version, string subVersion) 40 | { 41 | this.Version = version; 42 | this.SubVersion = subVersion; 43 | } 44 | 45 | /// 46 | /// 比较版本 47 | /// 48 | /// 49 | /// 50 | public int CompareTo(ProductionVersion? other) 51 | { 52 | var x = this; 53 | var y = other; 54 | 55 | if (y == null) 56 | { 57 | return 1; 58 | } 59 | 60 | var value = x.Version.CompareTo(y.Version); 61 | if (value == 0) 62 | { 63 | value = CompareSubVerson(x.SubVersion, y.SubVersion); 64 | } 65 | return value; 66 | 67 | static int CompareSubVerson(string subX, string subY) 68 | { 69 | if (subX.Length == 0 && subY.Length == 0) 70 | { 71 | return 0; 72 | } 73 | if (subX.Length == 0) 74 | { 75 | return 1; 76 | } 77 | if (subY.Length == 0) 78 | { 79 | return -1; 80 | } 81 | 82 | return StringComparer.OrdinalIgnoreCase.Compare(subX, subY); 83 | } 84 | } 85 | 86 | public override string ToString() 87 | { 88 | return $"{Version}{SubVersion}"; 89 | } 90 | 91 | /// 92 | /// 解析 93 | /// 94 | /// 95 | /// 96 | public static ProductionVersion Parse(string productionVersion) 97 | { 98 | string verion = productionVersion.Split("+")[0]; 99 | string subVersion = productionVersion.Split("+")[1]; 100 | return new ProductionVersion(Version.Parse(verion), subVersion); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /FastGithub/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using System; 3 | using System.IO; 4 | 5 | namespace FastGithub 6 | { 7 | 8 | class Program 9 | { 10 | /// 11 | /// 程序入口 12 | /// 13 | /// 14 | public static void Main(string[] args) 15 | { 16 | ConsoleUtil.DisableQuickEdit(); 17 | var contentRoot = Path.GetDirectoryName(Environment.ProcessPath); 18 | if (string.IsNullOrEmpty(contentRoot) == false) 19 | { 20 | Environment.CurrentDirectory = contentRoot; 21 | } 22 | var options = new WebApplicationOptions 23 | { 24 | Args = args, 25 | ContentRootPath = contentRoot 26 | }; 27 | CreateWebApplication(options).Run(singleton: true); 28 | } 29 | 30 | /// 31 | /// 创建host 32 | /// 33 | /// 34 | /// 35 | private static WebApplication CreateWebApplication(WebApplicationOptions options) 36 | { 37 | var builder = WebApplication.CreateBuilder(options); 38 | builder.ConfigureHost(); 39 | builder.ConfigureWebHost(); 40 | builder.ConfigureConfiguration(); 41 | builder.ConfigureServices(); 42 | 43 | var app = builder.Build(); 44 | app.ConfigureApp(); 45 | return app; 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /FastGithub/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "FastGithub": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /FastGithub/Startup.cs: -------------------------------------------------------------------------------- 1 | using FastGithub.Configuration; 2 | using FastGithub.FlowAnalyze; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Serilog; 10 | using Serilog.Sinks.Network; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Diagnostics.CodeAnalysis; 14 | using System.IO; 15 | using System.Net; 16 | using System.Text.Json; 17 | 18 | namespace FastGithub 19 | { 20 | /// 21 | /// 启动项 22 | /// 23 | static class Startup 24 | { 25 | /// 26 | /// 配置通用主机 27 | /// 28 | /// 29 | public static void ConfigureHost(this WebApplicationBuilder builder) 30 | { 31 | builder.Host.UseSystemd().UseWindowsService(); 32 | builder.Host.UseSerilog((hosting, logger) => 33 | { 34 | var template = "{Timestamp:O} [{Level:u3}]{NewLine}{SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"; 35 | logger 36 | .ReadFrom.Configuration(hosting.Configuration) 37 | .Enrich.FromLogContext() 38 | .WriteTo.Console(outputTemplate: template) 39 | .WriteTo.File(Path.Combine("logs", @"log.txt"), rollingInterval: RollingInterval.Day, outputTemplate: template); 40 | 41 | var udpLoggerPort = hosting.Configuration.GetValue(nameof(AppOptions.UdpLoggerPort), 38457); 42 | logger.WriteTo.UDPSink(IPAddress.Loopback, udpLoggerPort); 43 | }); 44 | } 45 | 46 | /// 47 | /// 配置web主机 48 | /// 49 | /// 50 | public static void ConfigureWebHost(this WebApplicationBuilder builder) 51 | { 52 | builder.WebHost.UseShutdownTimeout(TimeSpan.FromSeconds(1d)); 53 | builder.WebHost.UseKestrel(kestrel => 54 | { 55 | kestrel.NoLimit(); 56 | if (OperatingSystem.IsWindows()) 57 | { 58 | kestrel.ListenHttpsReverseProxy(); 59 | kestrel.ListenHttpReverseProxy(); 60 | kestrel.ListenSshReverseProxy(); 61 | kestrel.ListenGitReverseProxy(); 62 | } 63 | else 64 | { 65 | kestrel.ListenHttpProxy(); 66 | } 67 | }); 68 | } 69 | 70 | 71 | /// 72 | /// 配置配置 73 | /// 74 | /// 75 | public static void ConfigureConfiguration(this WebApplicationBuilder builder) 76 | { 77 | const string APPSETTINGS = "appsettings"; 78 | if (Directory.Exists(APPSETTINGS) == true) 79 | { 80 | foreach (var file in Directory.GetFiles(APPSETTINGS, "appsettings.*.json")) 81 | { 82 | var jsonFile = Path.Combine(APPSETTINGS, Path.GetFileName(file)); 83 | builder.Configuration.AddJsonFile(jsonFile, true, true); 84 | } 85 | } 86 | } 87 | 88 | 89 | /// 90 | /// 配置服务 91 | /// 92 | /// 93 | [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Dictionary))] 94 | public static void ConfigureServices(this WebApplicationBuilder builder) 95 | { 96 | var services = builder.Services; 97 | var configuration = builder.Configuration; 98 | 99 | services.Configure(configuration); 100 | services.Configure(configuration.GetSection(nameof(FastGithub))); 101 | 102 | services.AddConfiguration(); 103 | services.AddDomainResolve(); 104 | services.AddHttpClient(); 105 | services.AddReverseProxy(); 106 | services.AddFlowAnalyze(); 107 | services.AddHostedService(); 108 | 109 | if (OperatingSystem.IsWindows()) 110 | { 111 | services.AddPacketIntercept(); 112 | } 113 | } 114 | 115 | /// 116 | /// 配置应用 117 | /// 118 | /// 119 | public static void ConfigureApp(this WebApplication app) 120 | { 121 | app.UseHttpProxyPac(); 122 | app.UseRequestLogging(); 123 | app.UseHttpReverseProxy(); 124 | 125 | app.UseRouting(); 126 | app.DisableRequestLogging(); 127 | 128 | app.MapGet("/flowStatistics", context => 129 | { 130 | var flowStatistics = context.RequestServices.GetRequiredService().GetFlowStatistics(); 131 | var json = JsonSerializer.Serialize(flowStatistics, FlowStatisticsContext.Default.FlowStatistics); 132 | return context.Response.WriteAsync(json); 133 | }); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /FastGithub/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 58 | 59 | 60 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /FastGithub/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // 新增的子配置文件appsettings.*.json,重启应用程序才生效 3 | "FastGithub": { 4 | "HttpProxyPort": 38457, // http代理端口,linux/osx平台使用 5 | "FallbackDns": [ // 以下dns必须要支持tcp 6 | "8.8.8.8:53", 7 | "119.29.29.29:53", 8 | "114.114.114.114:53" 9 | ], 10 | "DomainConfigs": { 11 | "*.fastgithub.com": { // 域名的*表示除.之外0到多个任意字符 12 | "TlsSni": false, // 指示tls握手时是否发送SNI 13 | "TlsSniPattern": null, // SNI表达式,@domain变量表示取域名值 @ipaddress变量表示取ip @random变量表示取随机值,其它字符保留不替换 14 | "TlsIgnoreNameMismatch": false, // 是否忽略服务器证书域名不匹配,当不发送SNI时服务器可能发回域名不匹配的证书,默认为false 15 | "Timeout": null, // 请求超时时长,格式为"00:02:00",默认为null 16 | "IPAddress": null, // 请求的ip,默认为null 17 | "Destination": null, // 请求目的地,格式为绝对或相对Uri,默认null 18 | "Response": { // 阻断请求直接响应,设置了Response其它配置都不起作用了 19 | "StatusCode": 404, // 响应的状态码 20 | "ContentType": "text/plain;charset=utf-8", // 如果有ContentValue,就要指示ContentType 21 | "ContentValue": "这是一个用于示范配置的域名" // 自定义返回的内容,这是可选的 22 | } 23 | } 24 | } 25 | }, 26 | "Serilog": { 27 | "MinimumLevel": { 28 | "Default": "Information", 29 | "Override": { 30 | "Yarp": "Warning", 31 | "System": "Warning", 32 | "Microsoft": "Warning", 33 | "Microsoft.AspNetCore.Server.Kestrel": "Error" 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.amazonaws.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "s3.amazonaws.com": { 5 | "TlsIgnoreNameMismatch": true 6 | }, 7 | "*.s3.amazonaws.com": { 8 | "TlsIgnoreNameMismatch": true 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.bootcss.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "*.cloudflare.com": { 5 | "TlsSni": true 6 | }, 7 | "cdn.bootcss.com": { 8 | "TlsSni": true, 9 | "Destination": "https://cdnjs.cloudflare.com/ajax/libs/" 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.fastly.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "*.fastly.net": { 5 | "TlsIgnoreNameMismatch": true 6 | }, 7 | "*.*.fastly.net": { 8 | "TlsIgnoreNameMismatch": true 9 | }, 10 | "*.*.*.fastly.net": { 11 | "TlsIgnoreNameMismatch": true 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.github.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "github.com": { 5 | "TlsSni": false 6 | }, 7 | "api.github.com": { 8 | "TlsSni": false 9 | }, 10 | "githubstatus.com": { 11 | "TlsSni": false 12 | }, 13 | "gist.github.com": { 14 | "TlsIgnoreNameMismatch": true 15 | }, 16 | "vscode-auth.github.com": { 17 | "TlsSni": true 18 | }, 19 | "*.github.com": { 20 | "TlsIgnoreNameMismatch": true 21 | }, 22 | "*.github.io": { 23 | "TlsIgnoreNameMismatch": true 24 | }, 25 | "*.githubapp.com": { 26 | "TlsIgnoreNameMismatch": true 27 | }, 28 | "*.githubassets.com": { 29 | "TlsIgnoreNameMismatch": true 30 | }, 31 | "*.githubusercontent.com": { 32 | "TlsIgnoreNameMismatch": true 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.google.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "ajax.googleapis.com": { 5 | "TlsSni": true, 6 | "Destination": "https://gapis.geekzu.org/ajax/" 7 | }, 8 | "fonts.googleapis.com": { 9 | "TlsSni": true, 10 | "Destination": "https://fonts.geekzu.org/" 11 | }, 12 | "themes.googleusercontent.com": { 13 | "TlsSni": true, 14 | "Destination": "https://gapis.geekzu.org/g-themes/" 15 | }, 16 | "fonts.gstatic.com": { 17 | "TlsSni": true, 18 | "Destination": "https://gapis.geekzu.org/g-fonts/" 19 | }, 20 | "*.gravatar.com": { 21 | "TlsIgnoreNameMismatch": true 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.imgur.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "imgur.com": { 5 | "TlsIgnoreNameMismatch": true 6 | }, 7 | "*.imgur.com": { 8 | "TlsIgnoreNameMismatch": true 9 | }, 10 | "*.*.imgur.com": { 11 | "TlsIgnoreNameMismatch": true 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.microsoft.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "azure.com": { 5 | "TlsSni": true 6 | }, 7 | "*.azure.com": { 8 | "TlsSni": true 9 | }, 10 | "*.*.azure.com": { 11 | "TlsSni": true 12 | }, 13 | "*.*.*.azure.com": { 14 | "TlsSni": true 15 | }, 16 | "*.azureedge.net": { 17 | "TlsSni": true 18 | }, 19 | "*.visualstudio.com": { 20 | "TlsSni": true 21 | }, 22 | "*.*.visualstudio.com": { 23 | "TlsSni": true 24 | }, 25 | "*.live.com": { 26 | "TlsSni": true 27 | }, 28 | "*.*.live.com": { 29 | "TlsSni": true 30 | }, 31 | "microsoftonline.com": { 32 | "TlsSni": true 33 | }, 34 | "*.microsoftonline.com": { 35 | "TlsSni": true 36 | }, 37 | "windows.net": { 38 | "TlsSni": true 39 | }, 40 | "*.windows.net": { 41 | "TlsSni": true 42 | }, 43 | "*.*.windows.net": { 44 | "TlsSni": true 45 | }, 46 | "azurewebsites.net": { 47 | "TlsSni": true 48 | }, 49 | "*.azurewebsites.net": { 50 | "TlsSni": true 51 | }, 52 | "*.*.azurewebsites.net": { 53 | "TlsSni": true 54 | }, 55 | "*.vsassets.io": { 56 | "TlsSni": true 57 | }, 58 | "aadcdn.msauth.net": { 59 | "TlsSni": true 60 | }, 61 | "aadcdn.msftauth.net": { 62 | "TlsSni": true 63 | }, 64 | "static2.sharepointonline.com": { 65 | "TlsSni": true 66 | }, 67 | "az764295.vo.msecnd.net": { 68 | "TlsSni": true, 69 | "Destination": "https://vscode.cdn.azure.cn/" 70 | }, 71 | "*.*.msecnd.net": { 72 | "TlsSni": true 73 | }, 74 | "*.aspnetcdn.com": { 75 | "TlsSni": true 76 | }, 77 | "onedrive.live.com": { 78 | "TlsSni": false 79 | }, 80 | "*.onedrive.live.com": { 81 | "TlsSni": false 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.packages.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "*.nuget.org": { 5 | "TlsSni": true 6 | }, 7 | "*.maven.org": { 8 | "TlsSni": true 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /FastGithub/appsettings/appsettings.v2ex.json: -------------------------------------------------------------------------------- 1 | { 2 | "FastGithub": { 3 | "DomainConfigs": { 4 | "v2ex.com": { 5 | "TlsSni": false 6 | }, 7 | "*.v2ex.com": { 8 | "TlsSni": false 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | major-version-bump-message: "(breaking|major):" 2 | minor-version-bump-message: "(feature|minor):" 3 | patch-version-bump-message: "(fix|patch):" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 老九 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MacOSXConfig.md: -------------------------------------------------------------------------------- 1 | # MacOSX运行FastGithub配置教程 2 | 3 | ### 1 解决 "XXX" cannot be opened because the developer cannot be verified 4 | 打开终端进入FastGithub执行文件所在路径执行命令: 5 | `sudo xattr -d com.apple.quarantine *.*` 6 | 7 | ### 2 安装证书 8 | 打开FastGithub后,目录内会生成cacert目录,双击打开fastgithub.cer,系统弹出Keychain Access窗口,列表中双击FastGitHub,弹出证书详情窗口,展开Trust并选择Always Trust。 9 | 10 | 11 | 12 | 13 | 14 | ### 3 配置代理 15 | #### 3.1 自动代理 16 | 打开mac设置,网络,点击高级,选择代理,勾选网自动代理配置,填写FastGithub窗口提示的地址 17 | 18 | 19 | 20 | 21 | 22 | #### 3.2 手动代理 23 | 打开mac设置,网络,点击高级,选择代理,勾选网页代理(HTTP)及安全网页代理(HTTPS),填写FastGithub窗口提示的地址 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastGithub 2 | github加速神器,解决github打不开、用户头像无法加载、releases无法上传下载、git-clone、git-pull、git-push失败等问题。 3 | 4 | * 原仓库 https://github.com/dotnetcore/fastgithub 已经没有了,所以拿了最新版本改了自用。 5 | 6 | ### 1 写在前面 7 | * **fastgithub不具备“翻墙”功能,也没有相关的计划** 8 | * **fastgithub不支持Windows7等已被发行方停止支持的操作系统,并且也不会主动提供支持** 9 | * **fastgithub不能为您的游戏加速** 10 | * **fastgithub没有主动在github之外的任何渠道发布** 11 | 12 | ### 2 部署方式 13 | #### 2.1 windows-x64桌面 14 | * 双击运行FastGithub.UI.exe 15 | 16 | #### 2.2 windows-x64服务 17 | * `fastgithub.exe start` // 以windows服务安装并启动 18 | * `fastgithub.exe stop` // 以windows服务卸载并删除 19 | 20 | #### 2.3 linux-x64终端 21 | * `sudo ./fastgithub` 22 | * 设置系统自动代理为`http://127.0.0.1:38457`,或手动代理http/https为`127.0.0.1:38457` 23 | 24 | #### 2.4 linux-x64服务 25 | * `sudo ./fastgithub start` // 以systemd服务安装并启动 26 | * `sudo ./fastgithub stop` // 以systemd服务卸载并删除 27 | * 设置系统自动代理为`http://127.0.0.1:38457`,或手动代理http/https为`127.0.0.1:38457` 28 | 29 | #### 2.5 macOS-x64 30 | * 双击运行fastgithub 31 | * 安装cacert/fastgithub.cer并设置信任 32 | * 设置系统自动代理为`http://127.0.0.1:38457`,或手动代理http/https为`127.0.0.1:38457` 33 | * [具体配置详情](https://github.com/creazyboyone/FastGithub/blob/master/MacOSXConfig.md) 34 | 35 | #### 2.6 docker-compose一键部署 36 | * 准备好docker 18.09, docker-compose. 37 | * 在源码目录下,有一个docker-compose.yaml 文件,专用于在实际项目中,临时使用github.com源码,而做的demo配置。 38 | * 根据自己的需要更新docker-compose.yaml中的sample和build镜像即可完成拉github.com源码加速,并基于源码做后续的操作。 39 | 40 | ### 3 软件功能 41 | * 提供域名的纯净IP解析; 42 | * 提供IP测速并选择最快的IP; 43 | * 提供域名的tls连接自定义配置; 44 | * google的CDN资源替换,解决大量国外网站无法加载js和css的问题; 45 | 46 | ### 4 证书验证 47 | #### 4.1 git 48 | git操作提示`SSL certificate problem`
49 | 需要关闭git的证书验证:`git config --global http.sslverify false` 50 | 51 | #### 4.2 firefox 52 | firefox提示`连接有潜在的安全问题`
53 | 设置->隐私与安全->证书->查看证书->证书颁发机构,导入cacert/fastgithub.cer,勾选“信任由此证书颁发机构来标识网站” 54 | 55 | 56 | ### 5 安全性说明 57 | FastGithub为每台不同的主机生成自颁发CA证书,保存在cacert文件夹下。客户端设备需要安装和无条件信任自颁发的CA证书,请不要将证书私钥泄露给他人,以免造成损失。 58 | 59 | ### 6 合法性说明 60 | 《国际联网暂行规定》第六条规定:“计算机信息网络直接进行国际联网,必须使用邮电部国家公用电信网提供的国际出入口信道。任何单位和个人不得自行建立或者使用其他信道进行国际联网。” 61 | FastGithub本地代理使用的都是“公用电信网提供的国际出入口信道”,从国外Github服务器到国内用户电脑上FastGithub程序的流量,使用的是正常流量通道,其间未对流量进行任何额外加密(仅有网页原有的TLS加密,区别于VPN的流量加密),而FastGithub获取到网页数据之后发生的整个代理过程完全在国内,不再适用国际互联网相关之规定。 62 | -------------------------------------------------------------------------------- /Resources/MacOSXConfig/KeychainAccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/Resources/MacOSXConfig/KeychainAccess.png -------------------------------------------------------------------------------- /Resources/MacOSXConfig/autoproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/Resources/MacOSXConfig/autoproxy.png -------------------------------------------------------------------------------- /Resources/MacOSXConfig/cmdwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/Resources/MacOSXConfig/cmdwin.png -------------------------------------------------------------------------------- /Resources/MacOSXConfig/proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/Resources/MacOSXConfig/proxy.png -------------------------------------------------------------------------------- /Resources/MacOSXConfig/trust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creazyboyone/FastGithub/1dee410cd6730dc4a49a0705f442d2b08a900de1/Resources/MacOSXConfig/trust.png -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | fastgithub: 4 | image: slcnx/fastgithub 5 | network_mode: host 6 | restart: always 7 | volumes: 8 | - cacert:/fastgithub/cacert/ 9 | sample: 10 | depends_on: 11 | - fastgithub 12 | image: slcnx/ubuntu:18.04 13 | volumes: 14 | - cacert:/tmp/cacert 15 | - build_data:/build_data 16 | working_dir: /build_data 17 | restart: on-failure 18 | tty: true 19 | entrypoint: sh -c 'cp /tmp/cacert/fastgithub.cer /usr/local/share/ca-certificates/fastgithub.crt && update-ca-certificates && git clone https://github.com/kubernetes/kubernetes.git' 20 | command: "" 21 | environment: 22 | https_proxy: http://127.0.0.1:38457 23 | http_proxy: http://127.0.0.1:38457 24 | network_mode: host 25 | build: 26 | working_dir: /build_data 27 | depends_on: 28 | - sample 29 | image: nginx 30 | volumes: 31 | - build_data:/build_data 32 | volumes: 33 | cacert: {} 34 | build_data: {} 35 | -------------------------------------------------------------------------------- /pack.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cd ./publish 3 | 4 | # win-x64 5 | zip -r fastgithub_win-x64.zip fastgithub_win-x64 6 | 7 | # linux-x64 8 | chmod 777 ./fastgithub_linux-x64/fastgithub 9 | chmod 777 ./fastgithub_linux-x64/dnscrypt-proxy/dnscrypt-proxy 10 | zip -r fastgithub_linux-x64.zip fastgithub_linux-x64 11 | 12 | # linux-arm64 13 | chmod 777 ./fastgithub_linux-arm64/fastgithub 14 | chmod 777 ./fastgithub_linux-arm64/dnscrypt-proxy/dnscrypt-proxy 15 | zip -r fastgithub_linux-arm64.zip fastgithub_linux-arm64 16 | 17 | # osx-x64 18 | chmod 777 ./fastgithub_osx-x64/fastgithub 19 | chmod 777 ./fastgithub_osx-x64/dnscrypt-proxy/dnscrypt-proxy 20 | zip -r fastgithub_osx-x64.zip fastgithub_osx-x64 21 | 22 | # osx-arm64 23 | chmod 777 ./fastgithub_osx-arm64/fastgithub 24 | chmod 777 ./fastgithub_osx-arm64/dnscrypt-proxy/dnscrypt-proxy 25 | zip -r fastgithub_osx-arm64.zip fastgithub_osx-arm64 26 | -------------------------------------------------------------------------------- /publish.cmd: -------------------------------------------------------------------------------- 1 | set output=./publish 2 | if exist "%output%" rd /S /Q "%output%" 3 | dotnet publish -c Release -o "%output%/fastgithub_win-x64" ./FastGithub.UI/FastGithub.UI.csproj 4 | dotnet publish -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained -r win-x64 -o "%output%/fastgithub_win-x64" ./FastGithub/FastGithub.csproj 5 | dotnet publish -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained -r linux-x64 -o "%output%/fastgithub_linux-x64" ./FastGithub/FastGithub.csproj 6 | dotnet publish -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained -r linux-arm64 -o "%output%/fastgithub_linux-arm64" ./FastGithub/FastGithub.csproj 7 | dotnet publish -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained -r osx-x64 -o "%output%/fastgithub_osx-x64" ./FastGithub/FastGithub.csproj 8 | dotnet publish -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained -r osx-arm64 -o "%output%/fastgithub_osx-arm64" ./FastGithub/FastGithub.csproj --------------------------------------------------------------------------------