Program.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. using System.Buffers;
  2. using System.IO.Pipelines;
  3. using Microsoft.AspNetCore.Connections;
  4. using System.Net;
  5. using System.Security.Cryptography.X509Certificates;
  6. using DnsClient;
  7. using System.Text;
  8. #region INIT
  9. using Microsoft.Extensions.Logging.Console;
  10. using System.Net.Security;
  11. using System.Net.Sockets;
  12. var builder = WebApplication.CreateBuilder(args);
  13. //控制台日志格式
  14. builder.Services.AddLogging(opt =>
  15. {
  16. opt.AddSimpleConsole(p =>
  17. {
  18. p.TimestampFormat = "[dd HH:mm:ss] ";
  19. p.SingleLine = true;
  20. p.ColorBehavior = LoggerColorBehavior.Enabled;
  21. });
  22. });
  23. builder.WebHost.UseKestrel(opt =>
  24. {
  25. opt.Listen(IPAddress.Any, 0, lisOpt =>
  26. {
  27. lisOpt.UseConnectionHandler<NoSniProxyHandler>();
  28. });
  29. });
  30. await using var host = builder.Build();
  31. var logger = host.Services.GetRequiredService<ILogger<Program>>();
  32. logger.LogInformation("Hello, World!");
  33. await host.RunAsync();
  34. #endregion INIT
  35. public class NoSniProxyHandler : ConnectionHandler
  36. {
  37. private const string dnsServerName = "reliable-dns-server-in-hosts";
  38. private static readonly IPAddress? dnsServerIp = Dns.GetHostEntry(dnsServerName).AddressList.FirstOrDefault();
  39. private static readonly LookupClient lookup = new(dnsServerIp);
  40. public override async Task OnConnectedAsync(ConnectionContext connection)
  41. {
  42. var requestStream = connection.Transport.Input;
  43. var responseStream = connection.Transport.Output;
  44. var firstLine = await ReadLineAsync(requestStream);
  45. if (firstLine == null)
  46. {
  47. connection.Abort(new ConnectionAbortedException("Canceled: First line rad fail"));
  48. return;
  49. }
  50. var firstLineParts = firstLine.Split(' ', 3);
  51. if (firstLineParts.Length < 3)
  52. {
  53. connection.Abort(new ConnectionAbortedException("Canceled: First line bad"));
  54. return;
  55. }
  56. var method = firstLineParts[0];
  57. var url = firstLineParts[1];
  58. var ver = firstLineParts[2];
  59. var uri = new Uri(url);
  60. var targetHost = uri.Host;
  61. var result = await lookup.QueryAsync(targetHost, QueryType.A);
  62. var record = result.Answers.ARecords().FirstOrDefault();
  63. var ip = record?.Address;
  64. var tcpClient = new TcpClient();
  65. await tcpClient.ConnectAsync(new IPEndPoint(ip, 443));
  66. var ssl = new SslStream(tcpClient.GetStream());
  67. var sslOptions = new SslClientAuthenticationOptions
  68. {
  69. TargetHost = string.Empty, // Leave this empty to avoid sending SNI
  70. RemoteCertificateValidationCallback = (o, certificate, chain, errors) => VerifyServerCert(targetHost, certificate, chain, errors),
  71. };
  72. await ssl.AuthenticateAsClientAsync(sslOptions, new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token);
  73. ssl.Write(Encoding.ASCII.GetBytes($"{method} {uri.PathAndQuery} {ver}\r\n"));
  74. var outgoing = requestStream.CopyToAsync(ssl);
  75. var inbound = ssl.CopyToAsync(responseStream);
  76. await Task.WhenAll(outgoing, inbound);
  77. }
  78. private async Task<string?> ReadLineAsync(PipeReader requestStream)
  79. {
  80. while (true)
  81. {
  82. var readResult = await requestStream.ReadAsync();
  83. if (readResult.IsCanceled) return null;
  84. var (seq, exm) = ExtractLine(readResult.Buffer, out var line);
  85. requestStream.AdvanceTo(seq, exm);
  86. if (line != null) return line;
  87. }
  88. }
  89. private (SequencePosition pos, SequencePosition exm) ExtractLine(ReadOnlySequence<byte> buffer, out string? line)
  90. {
  91. var reader = new SequenceReader<byte>(buffer);
  92. if (reader.TryReadTo(out ReadOnlySpan<byte> span, "\r\n"u8))
  93. {
  94. if (span.Length > 4096) throw new InvalidDataException("Too long for line");
  95. line = Encoding.ASCII.GetString(span);
  96. return (reader.Position, reader.Position);
  97. }
  98. line = null;
  99. return (buffer.Start, buffer.End);
  100. }
  101. private bool VerifyServerCert(string targetHost, X509Certificate certificate, X509Chain? chain, SslPolicyErrors errs)
  102. {
  103. if (errs == SslPolicyErrors.None) return true;
  104. if (errs != SslPolicyErrors.RemoteCertificateNameMismatch) return false;
  105. if (certificate is not X509Certificate2 cert2) return false;
  106. // 比较证书名称和主机名称
  107. var certName = cert2.GetNameInfo(X509NameType.DnsName, false);
  108. if (certName.StartsWith("*."))
  109. {
  110. if (!targetHost.EndsWith(certName[2..], StringComparison.OrdinalIgnoreCase)) return false;
  111. }
  112. else if (!certName.Equals(targetHost, StringComparison.OrdinalIgnoreCase)) return false;
  113. // 验证证书的有效期
  114. if (DateTime.Now < cert2.NotBefore || DateTime.Now > cert2.NotAfter) return false;
  115. // 构建证书链
  116. if (chain == null) return false;
  117. chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; // 不检查吊销,太耗时了
  118. chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
  119. chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 10);
  120. chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
  121. var isValidChain = chain.Build(cert2);
  122. if (isValidChain) return true;
  123. foreach (X509ChainStatus chainStatus in chain.ChainStatus)
  124. {
  125. // 仅处理会影响安全性的错误状态
  126. if (chainStatus.Status == X509ChainStatusFlags.RevocationStatusUnknown ||
  127. chainStatus.Status == X509ChainStatusFlags.OfflineRevocation ||
  128. chainStatus.Status == X509ChainStatusFlags.NoError)
  129. {
  130. continue;
  131. }
  132. // 其他任何错误状态都认为证书无效
  133. return false;
  134. }
  135. return true;
  136. }
  137. }