Проблемы туннелирования через прокси-сервер с SslStream и TcpClient - PullRequest
0 голосов
/ 11 октября 2018

Мы используем TcpClient и TcpListener для связи между клиентским и серверным приложением.В некоторых случаях мы должны поддерживать подключение через прокси-сервер, и поэтому мы успешно реализовали «туннельный» подход из этой статьи .

Недавно мы «улучшили» наше общение, чтобы использоватьSslStream вместо отправки незашифрованных данных через Интернет, однако, похоже, это нарушило туннелирование прокси.

Сокращенная версия нашего кода выглядит следующим образом:

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 и т. Д.), Поэтому я совершенно уверен, что это правильный подход ввообще?

...