Search code examples
c#streamhttp-proxy

HTTP CONNECT stream.ReadToEnd takes infinite time


I'm trying to build a class which should be able to use HTTP CONNECT proxy tunnels. My current code hangs at DoHttpConnect() - "var retvar = await sr.ReadToEndAsync();" and never reaches Console.WriteLine(retvar);

Connection class:

 public class HttpConnect : BaseProxyModule
{
    public HttpConnect(HostPortCollection proxyInformation) : base(proxyInformation)
    {
    }

    public override async Task<Stream> OpenStreamAsync(HostPortCollection dstSrv)
    {
        var socket = new TcpClient();
        if (await SwallowExceptionUtils.TryExec(() => socket.ConnectAsync(_proxyInformation.Addr, _proxyInformation.Port)) && socket.Connected)
        {
            var nStream = socket.GetStream();
            var cmd = ProxyCommandBuildUtils.GenerateHttpConnectCommand(dstSrv.Host, dstSrv.Port);

            await nStream.WriteAsync(cmd, 0, cmd.Length);
            var sr = new StreamReader(nStream);
            var cLine = await sr.ReadLineAsync();
            var fLineElem = cLine.Split(' ');

            if (fLineElem.Length >= 1 && fLineElem[1] == "200")
            {
                await sr.ReadLineAsync();
                return nStream;
            }
        }
        return null;
    }

    internal class ProxyCommandBuildUtils
    {
        private const string HTTP_PROXY_CONNECT_CMD = "CONNECT {0}:{1} HTTP/1.1\r\nHost: {0}\r\n\r\n";

        public static byte[] GenerateHttpConnectCommand(string host, int port)
        {
            string connectCmd = String.Format(CultureInfo.InvariantCulture, HTTP_PROXY_CONNECT_CMD, host, port.ToString(CultureInfo.InvariantCulture));
            return connectCmd.GetBytes();
        }
    }

Usage:

      public static void Main(string[] args)
    {
        DoHttpConnect().Wait();
        Debugger.Break();
    }

    public static async Task DoHttpConnect()
    {
        //var hCon = new HttpConnect(new HostPortCollection("5.135.195.166", 3128)); //Doesn't work..
        var hCon = new HttpConnect(new HostPortCollection("109.75.213.146", 53281)); //Doesn't work either :/
        var pStream = await hCon.OpenStreamAsync(new HostPortCollection("www.myip.ch", 80));
        var request = FormatHttpRequest(new Uri("http://www.myip.ch/"));
        using (var sw = new StreamWriter(pStream))
        using (var sr = new StreamReader(pStream))
        {
            await sw.WriteLineAsync(request);

            var retvar = await sr.ReadToEndAsync(); //Takes till the end of time itself
            Console.WriteLine(retvar);
        }
        Debugger.Break();
    }
    private static string FormatHttpRequest(Uri url)
    {
        return $"GET {url.LocalPath}  HTTP/1.0\r\nHost: {url.DnsSafeHost}\r\n\r\n";
    }

Solution

  • The WriteLineAsync() call doesn't actually write out to the network immediately. Instead, the StreamWriter holds it in a buffer. Because of this, the server never actually gets your request, so won't send you a reply.

    You should force the buffer to write out to the network by doing one of the following:

    1. Turn on AutoFlush (reference) for the StreamWriter, which will make it flush the buffer with every call to WriteLineAsync()
    2. Explicitly call FlushAsync() (reference)
    3. Close the stream (which will flush the buffer) by rewriting the code like this:

      using (var sr = new StreamReader(pStream))
      {
          using (var sw = new StreamWriter(pStream))
          {
              await sw.WriteLineAsync(request);
          }
      
          var retvar = await sr.ReadToEndAsync();
          Console.WriteLine(retvar);
      }