Nano Hash - криптовалюты, майнинг, программирование

Туннелирование Java-прокси HTTPS

Я занимаюсь созданием прокси-сервера на Java; сервер правильно обрабатывает HTTP-трафик, но не может правильно туннелировать HTTPS-трафик. В соответствии с проектом IETF выполняется следующий процесс:

1) Я успешно получаю сообщение CONNECT от браузера, которое содержит HOST для подключения в виде простого текста.

2) Я анализирую это сообщение, чтобы извлечь информацию о хосте и успешно установить соединение с удаленным хостом.

3) Затем я отправляю клиенту сообщение об установлении соединения HTTP/1.0 200, а затем немедленно пытаюсь ретранслировать трафик с любой стороны соединения, используя приведенный ниже код.

Проблема заключается в том, что после того, как я возвращаю вышеуказанное сообщение 200 вместо отправки данных HTTPS, браузер, похоже, входит в бесконечный цикл и продолжает отправлять дальнейшие сообщения CONNECT на прокси. Вот код, который я использую для передачи данных между клиентом и хостом:

 public static void stageTunnelledConnection(Socket clientSocket,Socket targetHostSocket) throws IOException{

     //set client socket read timeout to 2 seconds. The targethost connection will ALREADY have been
     //set to this value at the time this method is called.
     clientSocket.setSoTimeout(2000);

     InputStream[] socketInputStreamsArr = new InputStream[]{clientSocket.getInputStream(),targetHostSocket.getInputStream()};

     OutputStream[] socketOutputStreamsArr = new OutputStream[]{clientSocket.getOutputStream(),targetHostSocket.getOutputStream()};

     //holds current socket index to read from, this will be switched between the two sockets
     //at 0 and 1 indexes of the sockets array respectively.
     int curReadIndex = 0;
     //this will be set according to the "curReadIndex" value and will typically be
     //the logical NOT of that value; that is, where curReadIndex equals 0 the curWriteIndex to will equal 1 and visa versa.
     int curWriteIndex = 1; 
     while(true){

        try{

         //attempt to read from socket stream at current index and write
         //to the socket at the alternate index.
         byte[] dataBuff = new byte[2048];

         int bytesRead = 0;
         //we read into the dataBuff this operation will block for
         //a max of 2 seconds should no data be available to read
         while((bytesRead = socketInputStreamsArr[curReadIndex].read(dataBuff)) != -1){

            //ByteArrayInputStream bais = new ByteArrayInputStream(dataBuff);

            //BufferedReader br = new BufferedReader(new InputStreamReader(bais));

            //System.out.println(br.readLine());

            //write the buffer to the outputsteam at the index 
            //computed and stored to the "curWriteIndex" var above.
            socketOutputStreamsArr[curWriteIndex].write(dataBuff);
            socketOutputStreamsArr[curWriteIndex].flush();

             //System.out.println("Bytes read=".concat(String.valueOf(dataBuff)));
            //System.out.println("wroteBytes: "+bytesRead);

         }



        }

        catch(SocketTimeoutException ste){

         //we switch read/write index each time a read timeout error occurs. I.e 
         //were there is no further data to read from the socket at the currrent read index.
         if(ste.getMessage().contains("Read")){
         //System.out.println("Switching connection.");
         curReadIndex = (curReadIndex == 0) ? 1 : 0;
         curWriteIndex = (curReadIndex == 0) ? 1 : 0;
         }
         else{

            //clientSocket.close();
            //targetHostSocket.close();
            ste.printStackTrace();
         }

        }
        catch(SocketException ioe){

            //if an input/output exception occurs we must close both sockets
             clientSocket.close();
             targetHostSocket.close();

             ioe.printStackTrace();

        }



     }


 }

**ВАЖНО: ** Поскольку фактические туннелируемые данные зашифрованы и, следовательно, непрозрачны для прокси-сервера, прокси-сервер должен быть готов к чтению/записи с любой стороны в любое время. Чтобы облегчить этот процесс в одном потоке, я устанавливаю относительно короткий тайм-аут сокета (2 секунды) с обеих сторон и вхожу в цикл, который чередует, с какой стороны он читает и записывает на каждой итерации, где нет доступных данных SocketTimeoutException происходит перехват, в этот момент переключается сторона для чтения, и цикл продолжает выполняться. Может ли эта стратегия, направленная на чтение из двух сокетов в одном потоке, вызывать проблему?

12.07.2015

  • Вы уверены, что рукопожатие с CONNECT и ответ (который вы не показываете) правильно? Если это неверно, это может объяснить, что браузер пытается снова с новым CONNECT. Вы можете добавить захват пакета соединения между клиентом и прокси, чтобы показать, что происходит на самом деле (используйте cloudshark.org). 12.07.2015

Ответы:


1
socketOutputStreamsArr[curWriteIndex].write(dataBuff);

Которые должны быть

socketOutputStreamsArr[curWriteIndex].write(dataBuff, 0, bytesRead);

Чтобы облегчить этот процесс в одном потоке, я устанавливаю относительно короткий тайм-аут сокета (2 секунды) с обеих сторон и вхожу в цикл, который чередует, с какой стороны он читает и записывает на каждой итерации, где нет доступных данных SocketTimeoutException происходит перехват, в этот момент переключается сторона для чтения, и цикл продолжает выполняться. Может ли эта стратегия, направленная на чтение из двух сокетов в одном потоке, вызывать проблему?

да. Вы должны использовать либо два потока, либо неблокирующий NIO. В противном случае вы просто добавляете много нежелательной задержки.

12.07.2015
  • Я думаю, что метод: write(dataBuff) эквивалентен: write(dataBuff,0,dataBuff.length) Я использую первый для простоты. 12.07.2015
  • Если, конечно, OutputStream не запишет нулевые байты, где dataBuff содержит меньше 2048 байт. Я изменю код, чтобы использовать перегруженный метод записи, как вы описали, и сообщу здесь. 12.07.2015
  • Хаха РАБОТАЕТ!!! Спасибо Вам большое!! кто бы мог подумать, что потребуется такое простое изменение (т.е. использование перегруженного метода write()). Оказывается, моя стратегия чтения из двух сокетов с одним потоком тоже отлично работает. Как я уже упоминал ранее, ошибка должна была происходить из-за того, что ВЕСЬ буфер записывается в выходной поток, даже если он содержит МЕНЬШЕ, чем предписанные байты 2048. Я бы подумал, что реализация выходного потока будет достаточно разумной, чтобы опустить нулевые индексы в предоставленном массиве. 12.07.2015
  • @GilesThompson Это не эквивалентно и не проще. Это просто неправильно. И дело не в том, что реализация OutputStream «достаточно умна, чтобы опускать нулевые байты». Лишние байты вовсе не обязательно нулевые. Они нетронуты предыдущей итерацией. Дело в том, что ваш код говорит ему записать весь буфер, что он и делает. Это то, что прописано в контракте, и так оно должно себя вести. 12.07.2015
  • Вы, конечно, правы, я только что просмотрел документы, относящиеся к обоим методам. Большое спасибо за оперативный и полезный ответ. 12.07.2015
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..