0

I'm in the process of building a Proxy Server in Java; the server handles HTTP traffic correctly but fails to correctly tunnel HTTPS traffic. As per the IETF draft, the following process is undertaken:

1) I successfully receive the CONNECT message from the browser that contains the HOST to connect to in plain text.

2)I parse this message to extract the host details and successfully establish a connection to the remote host.

3)I then send a HTTP/1.0 200 Connection established message back to the client and then immediately attempt to relay traffic from either side of the connection using the code below.

The problem is after I return the above 200 message instead of sending HTTPS data, the browser seems to enter into an infinite loop and keep sending further CONNECT messages to the proxy. Here is the code I use to relay the data between the client and the host:

 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();

        }



     }


 }

**IMPORTANT: ** As the actual data being tunnelled is encrypted and thus opaque to the proxy, the proxy must be prepared to read/write from either side at any time. In order to facilitate this process in a single thread I set a relatively short Socket Timeout (2 seconds) on both sides and enter into a loop which alternates which side it reads from and writes to on each iteration, where no data is available a SocketTimeoutException occurs which is caught, the side to read from is switched at this point and the loop continues to execute. Could this strategy that aims to read from two sockets in a single thread be causing the problem?

1
  • Are you sure that the handshake with CONNECT and response (which you don't show) is correct? If it is not correct it might explain that the browser tries again with a new CONNECT. You might add a packet capture of the connection between client and proxy to show what's really going on (use cloudshark.org). Commented Jul 12, 2015 at 8:20

1 Answer 1

1
socketOutputStreamsArr[curWriteIndex].write(dataBuff);

That should be

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

In order to facilitate this process in a single thread I set a relatively short Socket Timeout (2 seconds) on both sides and enter into a loop which alternates which side it reads from and writes to on each iteration, where no data is available a SocketTimeoutException occurs which is caught, the side to read from is switched at this point and the loop continues to execute. Could this strategy that aims to read from two sockets in a single thread be causing the problem?

Yes. You should use either two threads or non-blocking NIO. Otherwise you're just adding lots of unwanted latency.

Sign up to request clarification or add additional context in comments.

5 Comments

I think the method: write(dataBuff) is equivalent to: write(dataBuff,0,dataBuff.length) I use the former for simplicity.
Unless of course the OutputStream will write null bytes where the dataBuff contains less than the 2048 bytes. I'll change the code to use the overloaded write method as you have described and report back here.
Haha It WORKS!!! Thank you very much indeed!! who would have thought such a simple change (i.e using the overloaded write() method would be all that was required. Turns out my strategy of reading from two sockets with one thread works perfectly too. Like I previously intimated the error must have been occurring due to the ENTIRE buffer being written to the output stream even when it contains LESS that the prescribed 2048 bytes. I would have thought the outputstream implementation would have been intelligent enough to omit null indexes in the provided array.
@GilesThompson It isn't equivalent, and it isn't simpler. It's just wrong. And it's not a matter of an OutputStream implementation being 'intelligent enough to omit null bytes'. The excess bytes aren't necessarily null at all. They are undisturbed from the previous iteration. It's a matter of your code telling it to write the entire buffer, so it does. That's what's in the contract, and that's how it must behave.
You are of course correct, I have just reviewed the docs that pertain to both methods. Many thanks for your prompt and helpful answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.