Как реализовать SSL-туннель через прозрачный прокси?

Я прочитал SSL TUNNELING INTERNET-DRAFT от декабря 1995 г. и настроил HTTP прозрачный прокси, который отлично работает с незашифрованным трафиком.

Прочитав вышеизложенное, а также погуглив свои мозги, общепринятый метод создания туннеля для защищенного трафика через прокси выглядит следующим образом:

подключиться к запрошенному хосту, затем заставить прокси-сервер отправить подтверждающее сообщение «HTTP 200 ...» обратно клиенту, а затем с этого момента просто передать весь дальнейший трафик данных между клиентом и сервером.

Однако, когда я пытаюсь это сделать, клиент (браузер Chrome) отвечает на сообщение «HTTP 200 ...» тремя символами крыла, которые я пересылаю удаленному хосту. В этот момент ответа нет, и соединение не установлено.

Вот код, который я использую для этого после подключения к хосту:

if((*request=='C')&&(*(request+1)=='O')&&(*(request+2)=='N')&&(*(request+3)=='N'))
{

    int recvLen;

    send(output,htok,strlen(htok),0); //htok looks like "HTTP/1.0 200 Connection Established\nProxy-Agent: this_proxy\r\n\r\n"

    std::memset(buff,0,bSize);
    int total;
        int bytes;
        int n;
        char cdata[MAXDATA];
        while ((recvLen = recv(output, buff, bSize-1,0)) > 0) //recving from client - here we get wingdings
        {
            memset(cdata,0, MAXDATA);
            strcat(cdata, buff);
            while(recvLen>=bSize-1)//just in case buff is too small
            {
                std::memset(buff,0,bSize);
                recvLen=recv(output,buff,bSize-1,0);
                strcat(cdata, buff);
            }

            total = 0;
            bytes = strlen(cdata);                      
            cout << cdata << endl;//how I see the wingdings
            while (total < strlen(cdata))
            {       
                n = send(requestSock, cdata + total, bytes,0);//forwarding to remote host
                if(n == SOCKET_ERROR)
                {
                    cout << "secure sending error" << endl;
                    break;
                }
                total += n;
                bytes -= n;
            }

            std::memset(buff,0,bSize);
            recvLen=recv(requestSock, buff, bSize,0);//get reply from remote host
            if (recvLen > 0)
            {
                do
                {
                    cout<<"Thread "<<threadid<<" [Connection:Secure]: "<<recvLen<<endl;


                    send(output, buff, recvLen,0);//forward all to client

                    recvLen= recv(requestSock, buff, bSize,0);

                    if(0==recvLen || SOCKET_ERROR==recvLen)         
                    {
                        cout<<"finished secure receiving or socket error"<<endl;
                        break;
                    }

                }while(true);
            }
                      }//end while, loop checks again for client data

Может ли кто-нибудь заметить ошибку моего пути?


person Philip Langford    schedule 06.03.2012    source источник
comment
Вызов send() может отправить меньше, чем recvLen, и вы игнорируете этот факт. Другая проблема связана с обработкой двоичных данных в SSL как текста (ваш код может работать для текстовых HTTP-запросов, но все равно не будет работать с двоичными данными).   -  person Eugene Mayevski 'Callback    schedule 06.03.2012


Ответы (2)


Ваш код намного сложнее, чем необходимо. Просто прочитайте массив символов, сохраните возвращенную длину и запишите столько байтов из того же массива в цикле, пока recv() не вернет ноль. 4 строки кода, включая две для фигурных скобок. Не пытайтесь собрать все входящее сообщение целиком, просто ретранслируйте все, что приходит, по мере поступления. В противном случае вы просто добавляете задержку и программируете ошибки. Полностью избавьтесь от всех вызовов strXXX().

person user207421    schedule 06.03.2012
comment
Первоначально я так и делал, но потом хотел посмотреть, какие данные поступают от клиента, когда ничего не происходит. Приветствие клиента должно состоять из более чем 3 крылатых символов... - person Philip Langford; 06.03.2012
comment
@PhilipLangford Обработка двоичных данных как ASCII не даст вам ничего полезного. Точно так же бесполезно называть их «крылатыми». Каково фактическое значение байтов? - person user207421; 06.03.2012
comment
Я последовал вашему совету и упростил передачу, и теперь я могу видеть обмен байтами, однако моя логика вызывает бесконечный цикл, когда удаленный хост никогда не выполняет свой запрос. Псевдокод: 'пока (получено от клиента > 0) { отправить на хост; while(recv from host›0) отправить клиенту; } - person Philip Langford; 06.03.2012
comment
recv() и send() возвращают 0, если соединение было разорвано. Вам нужно проверить их оба на возвращаемые значения ‹ 1, чтобы вы могли разорвать цикл. Вы также не должны запускать 2 отдельных цикла. Используйте один цикл, который вызывает select() с обоими сокетами, чтобы определить, когда любое соединение может быть прочитано. Когда одно из соединений доступно для чтения, используйте ioctlsocket(), чтобы определить, сколько байтов доступно, recv() только столько байтов, send() байтов для другого соединения и вернитесь к select(). Таким образом, оба соединения могут отправлять данные друг другу одновременно. - person Remy Lebeau; 07.03.2012
comment
@PhilipLangford Как я уже сказал выше, выполняйте цикл до тех пор, пока recv () не вернет ноль. - person user207421; 07.03.2012
comment
@RemyLebeau send() не вернет 0, если соединение было разорвано. Он либо преуспеет и вернет положительное значение из-за буфера отправки сокета, либо обнаружит предыдущий сброс и вернет -1 с errno == ECONNRESET. - person user207421; 07.03.2012
comment
Winsock не использует errno, но я видел, как send() возвращает 0 при корректном отключении, как это делает recv(). - person Remy Lebeau; 07.03.2012
comment
@RemyLebeau Нет, я занимаюсь программированием TCP/IP уже 25 лет. send() не может возвращать ноль в блокирующем режиме, если указанная длина не равна нулю; в противном случае предполагается вернуть ненулевое количество отправленных байтов, или -1 и errno. Winsock не упоминается в OP, хотя это следует из использования ov SOCKET_ERROR; во всяком случае мой смысл понятен. - person user207421; 07.03.2012
comment
Таким образом, в сумме я должен зацикливать функцию select() до тех пор, пока клиент или хост recv() не вернет ноль, и это позволит сформировать туннель SSL...? - person Philip Langford; 08.03.2012
comment
В яблочко. Есть еще одна тонкость, которую я до сих пор оставлял. Когда вы получаете ноль от recv(), отключите вывод на другом сокете пары (т.е. передайте EOS) и закройте оба сокета тогда и только тогда, когда вы уже отключили вывод сокета, в котором вы получили ноль recv( ) из. В противном случае вы рискуете преждевременно закрыться: примите во внимание, что входящее закрытие могло быть только само завершением работы, и данные могут все еще передаваться в другом направлении. - person user207421; 08.03.2012
comment
Я просто хотел бы сказать, что благодаря совету в этой теме у меня отлично работает туннель. Stirling усилия, вы, ребята, герои! - person Philip Langford; 10.03.2012

Я не думаю, что вы должны делать предположение, что трафик не содержит символов ASCII NUL:

            strcat(cdata, buff);
        }

        total = 0;
        bytes = strlen(cdata);

Если в потоке есть символы ASCII NUL, это не удастся.

person sarnold    schedule 06.03.2012
comment
Хорошая точка зрения. Я сделал это вместо использования std::string, чтобы сэкономить несколько циклов, но сетевые операции, вероятно, сводят на нет эффект этого. - person Philip Langford; 06.03.2012
comment
std::string также будет плохо обрабатывать 0x00 байт. Двоичные данные являются двоичными и не могут обрабатываться с помощью строковых классов. Ответ EJP предлагает лучший подход, чем я, - вообще не пытайтесь интерпретировать данные. - person sarnold; 06.03.2012