Http 隧道不适用于某些网站

Http tunnel does not work for some websites

我写了一个简单的http隧道代理服务器:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace TrafficMonitor
{
    public class ProxyServer
    {
        const int BufferSize = 255;
        public int Port { get; set; } = 9999;
        private static Regex httpRegex = new Regex(@"^(?<method>[a-zA-Z]+)\s(?<url>.+)\sHTTP/(?<major>\d)\.(?<minor>\d+)$");

        public async Task RunServerForEver()
        {
            var listner = new TcpListener(IPAddress.Any, Port);
            listner.Start();
            Console.WriteLine($"Listening on port {Port}");
            try
            {
                while (true)
                {
                    var client = await listner.AcceptTcpClientAsync();
                    ThreadPool.QueueUserWorkItem(HandleClient, client);
                }
            }
            finally
            {
                listner.Stop();
                Console.WriteLine("Server stopped");
            }
        }

        void HandleClient(object state)
        {
            TcpClient client = (TcpClient)state;
            Console.WriteLine($"New Connection Received: #{client.Client.Handle}");
            try
            {
                byte[] bytes = new byte[BufferSize];
                using (client)
                using (var stream = client.GetStream())
                {
                    int i = stream.Read(bytes, 0, bytes.Length);
                    var data = Encoding.ASCII.GetString(bytes, 0, i);
                    Console.WriteLine("Received: {0}", data);
                    if (!ParseHostPort(data, out string host, out int port))
                    {
                        client.Close();
                        return;
                    };
                    HandleClientIO(stream, host, port);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        bool ParseHostPort(string data, out string host, out int port)
        {
            host = null;
            port = 0;
            var lines = data.Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
            if (lines.Length == 0)
            {
                return false;
            }
            var connectMatch = httpRegex.Match(lines[0]);
            if (!connectMatch.Success)
            {
                return false;
            }
            if (connectMatch.Groups["method"].Value != "CONNECT")
            {
                return false;
            }
            var url = connectMatch.Groups["url"].Value;
            var uri = new Uri("http://" + url);
            host = uri.Host;
            port = uri.Port;
            return true;
        }
        async Task SendMessage(NetworkStream stream, string data)
        {
            byte[] msg = Encoding.ASCII.GetBytes(data);
            await stream.WriteAsync(msg, 0, msg.Length);            
        }
        void HandleClientIO(NetworkStream stream, string host, int port)
        {
            TcpClient client = new TcpClient();
            if (!client.ConnectAsync(host, port).Wait(10000))
            {
                throw new Exception("Could not connect to host: " + host + ":" + port);
            }
            SendMessage(stream, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\n\r\n").Wait();
            using (var targetStream = client.GetStream())
            {
                Task send = SendClientData(stream, targetStream);
                Task receive = ReceiveClientData(stream, targetStream);
                Parallel.Invoke(
                    async () => await send,
                    async () => await receive
                    );
                Task.WaitAll(send, receive);
            }
        }
        async Task SendClientData(NetworkStream stream, NetworkStream targetStream)
        {
            await Task.Yield();
            try
            {
                int r;
                byte[] sendBuffer = new byte[BufferSize];
                while ((r = await stream.ReadAsync(sendBuffer, 0, sendBuffer.Length)) != 0)
                {
                    var data = Encoding.ASCII.GetString(sendBuffer, 0, r);
                    //Console.WriteLine("Received: {0}", data);

                    await targetStream.WriteAsync(sendBuffer, 0, r);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Send Connection Failed: " + ex.Message);
            }
        }
        async Task ReceiveClientData(NetworkStream stream, NetworkStream targetStream)
        {
            await Task.Yield();
            try
            {
                byte[] receiveBuffer = new byte[BufferSize];
                int i;
                while ((i = await targetStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length)) != 0)
                {
                    //var data2 = Encoding.ASCII.GetString(receiveBuffer, 0, i);
                    //Console.WriteLine("Remote: {0}", data2);
                    await stream.WriteAsync(receiveBuffer, 0, i);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Receive Connection Failed: " + ex.Message);
            }
        }
    }
}

并简单地在 main 中使用:

class Program
    {
        static void Main(string[] args)
        {
            var server = new ProxyServer();
            server.RunServerForEver().Wait();
        }
    }

我执行代理并在 Chrome 和 Firefox 中对其进行测试,它适用于许多 https 和 http 网站。但不幸的是,有些网站不响应初始 SSL 握手,代理等待响应直到超时。

例如 'https://google.com' 有效,但 https://github.com/ 无效。

可能与网站支持的TLS版本有关

尽管 TLS 1.0 和 TLS 1.1 已被弃用,google 仍然启用它们,而 github 则没有。

Here您可以检查您的 http 隧道服务器不能正常工作的网站的 TLS 版本,并将它们与正常工作的网站进行比较。

经过几个小时的尝试和错误后,我发现我应该使用更大的缓冲区大小!

更改此行解决了问题:

const int BufferSize = 8192;

我不知道为什么,但显然当以较小的缓冲区大小发送多个数据块时,SSL 握手不起作用。