How to tunnel an SSL connection correctly between two NetworkStreams?
HttpWebRequest
doesn't support SOCKS proxies, so after a long research I've determined, that the best way to add SOCKS support for WebRequest
and WebClient
(without reinventing the wheel, like SocksHttpWebRequest does) was to create a temporary HTTP proxy which forwards any incoming request to the SOCKS proxy.
Forwarding an HTTP request is easy, and works like charm.
Forwarding an HTTPS request should be theoretically easy too:
The
HttpWebRequest
connects to our HTTP proxy, and sends the following:CONNECT google.com:443 HTTP/1.0 Host: google.com
We connect to
google.com:443
through the SOCKS proxy, and then send back the following to the client:HTTP/1.0 200 Tunnel established
According to this document, at this point, we copy all the bytes between the streams, and when one closes the connection, we close the other one too.
However, copying from one NetworkStream
to another doesn't seem to work like I expected. At one point, Read()
seems to hang for no apparent reason. If I set timeouts, regardless how big ones, I get certificate errors.
The stripped down version of the code I'm using right now:
/// <summary>
/// In theory, forwards any incoming bytes from one stream to another.
/// </summary>
/// <param name="httpProxyStream">The HTTP proxy, which we opened for <code>HttpWebRequest</code>.</param>
/// <param name="socksProxyStream">The SOCKS proxy, which we connect to and forward the traffic from the HTTP proxy.</param>
private void TunnelRequest(NetworkStream httpProxyStream, NetworkStream socksProxyStream)
{
while (true)
{
try
{
if (httpProxyStream.DataAvailable)
{
CopyStreamToStream(httpProxyStream, socksProxyStream);
}
if (socksProxyStream.DataAvailable)
{
CopyStreamToStream(socksProxyStream, httpProxyStream);
}
}
catch
{
break;
}
}
}
/// <summary>
/// Copies the first stream's content from the current position to the second stream.
/// </summary>
/// <param name="source">The source stream.</param>
/// <param name="destionation">The destionation stream.</param>
/// <param name="flush">if set to <c>true</c>, <c>Flush()</c> will be called on the destination stream after finish.</param>
/// <param name="bufferLength">Length of the buffer.</param>
public static void CopyStreamToStream(Stream source, Stream destionation, bool flush = true, int bufferLength = 4096)
{
var buffer = new byte[bufferLength];
while (true)
{
int i;
try
{
i = source.Read(buffer, 0, bufferLength);
}
catch
{
break;
}
if (i == 0)
{
break;
}
destionati开发者_StackOverflow社区on.Write(buffer, 0, i);
}
if (flush)
{
destionation.Flush();
}
}
The full class is available here: HttpToSocks.cs
I'm stuck on this piece of code for days now. The task is so simple, yet it just won't work... Please help me regain my sanity.
EDIT: I am aware that the while(true)
is not my greatest line, but after lots of variations, this remained in the code to make sure it won't exit prematurely, and that's why I'm getting certificate errors.
EDIT 2: The problem has been fixed. The full class is available in the git repository, if anyone's interested.
精彩评论