使用SslStream和TcpClient通过代理服务器进行隧道传输时出现问题

时间:2018-10-11 05:59:34

标签: c# .net tcpclient http-proxy

我们正在使用TcpClient和TcpListener在客户端和服务器应用程序之间进行通信。在某些情况下,我们必须支持通过代理服务器进行连接,因此我们已经成功实现了this article中的“隧道”方法。

最近,我们已“改进”我们的通信以使用SslStream,而不是通过Internet发送未加密的数据,但这似乎已经破坏了代理隧道。

我们的代码的摘要版本如下:

private bool Connect()
{
    var uriBuilder = new UriBuilder
    {
        Scheme = Uri.UriSchemeHttp,
        Host = _proxyServerHost,
        Port = _proxyServerPort
    };

    var proxyUri = uriBuilder.Uri;

    var request = WebRequest.Create(
        "http://" + _targetHost + ":" + _targetPort);

    var webProxy = new WebProxy(proxyUri);

    request.Proxy = webProxy;
    request.Method = "CONNECT";

    if (_proxyUserName == null)
    {
        var credentials = new NetworkCredential(
                       _proxyUserName, _proxyPassword);

        webProxy.Credentials = credentials;
    }

    HttpWebResponse response;

    logger.InfoExt("{83CAAA66-3004-4C13-8F9A-5B1E23063E53}", $"Sending CONNECT command to the proxy server");

    try
    {
        response = (HttpWebResponse)request.GetResponse();
    }
    catch (Exception ex)
    {
        logger.Error(ex, "{DF8498B5-A4CC-49ED-A928-BCB6D7D087D6}", "Error getting HTTP Response");
        return false;
    }                

    if (response.StatusCode != HttpStatusCode.OK)
    {
        logger.ErrorExt("{2D4040D6-9477-479E-B25D-2DBB58273265}", 
                       $"Not able to connect to proxy server.  Response code: {response.StatusCode}");
        ConnectionStatus = eSocketConnectionStatus.NotConnected;
        return false;
    }

    logger.InfoExt("{1E522215-3DB0-4ED7-8E6A-E2AD1AFD82D9}", $"Connected to the proxy server");

    var responseStream = response.GetResponseStream();

    const BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Instance;

    var rsType = responseStream.GetType();
    var connectionProperty = rsType.GetProperty("Connection", Flags);

    var connection = connectionProperty.GetValue(responseStream, null);
    var connectionType = connection.GetType();
    var networkStreamProperty = connectionType.GetProperty("NetworkStream", Flags);

    var networkStream = networkStreamProperty.GetValue(connection, null);
    var nsType = networkStream.GetType();
    var socketProperty = nsType.GetProperty("Socket", Flags);
    var socket = (Socket)socketProperty.GetValue(networkStream, null);

    _tcpClient = new TcpClient { Client = socket };

    var sslStream = new SslStream(_tcpClient.GetStream(), true,
                                        new RemoteCertificateValidationCallback(ValidateServerCertificate), null);

    try
    {
        sslStream.AuthenticateAsClient(_targetHost);
    }
    catch (Exception ex)
    {
        logger.Error(ex, "{CA1D5BDB-B85D-41AE-B63D-1F4804765FC6}", "Error authenticating SSL stream");
        _tcpClient.Close();
        ConnectionStatus = eSocketConnectionStatus.ConnectionFailedToInitiate;
        return false;
    }

    _tcpClientStream = sslStream;

    pfnCallBack = new AsyncCallback(OnDataRecevied);
    _tcpClientStream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, pfnCallBack, null);

    return true;
}

private void OnDataReceived(IAsyncResult asyn)
{
    var iRx = _tcpClientStream.EndRead(asyn);

    if (iRx <= 0)
    {
        // ERROR so disconnect
        return;
    }   
}

我们遇到的问题是,当我们调用BeginRead时,回调立即被调用,EndRead返回0字节,根据MSDN文档,这意味着套接字已关闭,因此我们结束了连接。

如果不使用

_tcpClientStream = sslStream;

我们使用

_tcpClientStream = _tcpClient.GetStream();

(即我们不使用SSL),然后代码可以正常运行,并且没有问题。

有人可以提供有关此主题的任何建议吗?

  • 使用完全相同的方法并仅打开SSL流的想法在根本上存在缺陷吗?
  • 我已经在计算机上安装了Fiddler,并且在使用Fiddler代理时似乎可以正常工作,但是当软件通过用户的“适当”代理服务器运行时,则无法正常工作
  • 代理服务器已将服务器IP和端口列入白名单,因此不应将其阻止
  • Fiddler是设置测试代理服务器以确认我们的实现正确的“最佳方法”吗?

“启发”我们方法的代码块似乎非常流行(出现在众多Github存储库,其他Stackoverflow问题等中),所以我很有信心,这通常是正确的方法? >

0 个答案:

没有答案