[tor-commits] [orbot/master] implementing http server for meek-client VPN bypass

n8fr8 at torproject.org n8fr8 at torproject.org
Fri Apr 3 17:04:06 UTC 2015


commit 096eae705baeaa05e703efbdfc0cd52b965699cb
Author: Nathan Freitas <nathan at freitas.net>
Date:   Sat Feb 14 01:54:56 2015 -0500

    implementing http server for meek-client VPN bypass
---
 src/org/torproject/android/service/TorService.java |   16 +-
 src/org/torproject/android/vpn/HttpProxy.java      |  606 ++++++++++++++++++++
 .../torproject/android/vpn/OrbotVpnService.java    |   25 +-
 3 files changed, 633 insertions(+), 14 deletions(-)

diff --git a/src/org/torproject/android/service/TorService.java b/src/org/torproject/android/service/TorService.java
index 81fdf90..01fa339 100644
--- a/src/org/torproject/android/service/TorService.java
+++ b/src/org/torproject/android/service/TorService.java
@@ -2120,9 +2120,9 @@ public class TorService extends Service implements TorServiceConstants, TorConst
         {
 	        if (mUseVPN) //set the proxy here if we aren't using a bridge
 	        {
-	        	String proxyType = "socks5";
-	        	String proxyHost = "127.0.0.1";
-	        	int proxyPort = 9999;
+	        	String proxyType = "http";//"socks5";
+	        	String proxyHost = "10.0.0.1";
+	        	int proxyPort = 8888;//9999;
 	            updateConfiguration(proxyType + "Proxy", proxyHost + ':' + proxyPort, false);
 	
 	        }
@@ -2244,11 +2244,11 @@ public class TorService extends Service implements TorServiceConstants, TorConst
 		        
             	if (mUseVPN)
                 {
-                	proxyType = "socks5";
-                	String proxyHost = "127.0.0.1";
-                	int proxyPort = 9999;
-
-            		proxyBridge = " proxyurl=" + proxyType + "://" + proxyHost + ':' + proxyPort;
+                	proxyType = "http"; //"socks5";
+            		String proxyHost = "10.0.0.1";
+                	int proxyPort = 8888; //9999;
+            		
+            		proxyBridge = " proxy=" + proxyType + "://" + proxyHost + ':' + proxyPort;
             		
             	}
             	else if (proxyType != null && proxyType.length() > 0)
diff --git a/src/org/torproject/android/vpn/HttpProxy.java b/src/org/torproject/android/vpn/HttpProxy.java
new file mode 100644
index 0000000..617788b
--- /dev/null
+++ b/src/org/torproject/android/vpn/HttpProxy.java
@@ -0,0 +1,606 @@
+package org.torproject.android.vpn;
+/* <!-- in case someone opens this in a browser... --> <pre> */
+/*
+ * This is a simple multi-threaded Java proxy server
+ * for HTTP requests (HTTPS doesn't seem to work, because
+ * the CONNECT requests aren't always handled properly).
+ * I implemented the class as a thread so you can call it 
+ * from other programs and kill it, if necessary (by using 
+ * the closeSocket() method).
+ *
+ * We'll call this the 1.1 version of this class. All I 
+ * changed was to separate the HTTP header elements with
+ * \r\n instead of just \n, to comply with the official
+ * HTTP specification.
+ *  
+ * This can be used either as a direct proxy to other
+ * servers, or as a forwarding proxy to another proxy
+ * server. This makes it useful if you want to monitor
+ * traffic going to and from a proxy server (for example,
+ * you can run this on your local machine and set the
+ * fwdServer and fwdPort to a real proxy server, and then
+ * tell your browser to use "localhost" as the proxy, and
+ * you can watch the browser traffic going in and out).
+ *
+ * One limitation of this implementation is that it doesn't 
+ * close the ProxyThread socket if the client disconnects
+ * or the server never responds, so you could end up with
+ * a bunch of loose threads running amuck and waiting for
+ * connections. As a band-aid, you can set the server socket
+ * to timeout after a certain amount of time (use the
+ * setTimeout() method in the ProxyThread class), although
+ * this can cause false timeouts if a remote server is simply
+ * slow to respond.
+ *
+ * Another thing is that it doesn't limit the number of
+ * socket threads it will create, so if you use this on a
+ * really busy machine that processed a bunch of requests,
+ * you may have problems. You should use thread pools if
+ * you're going to try something like this in a "real"
+ * application.
+ *
+ * Note that if you're using the "main" method to run this
+ * by itself and you don't need the debug output, it will
+ * run a bit faster if you pipe the std output to 'nul'.
+ *
+ * You may use this code as you wish, just don't pretend 
+ * that you wrote it yourself, and don't hold me liable for 
+ * anything that it does or doesn't do. If you're feeling 
+ * especially honest, please include a link to nsftools.com
+ * along with the code. Thanks, and good luck.
+ *
+ * Julian Robichaux -- http://www.nsftools.com
+ */
+import java.io.*;
+import java.net.*;
+import java.nio.channels.SocketChannel;
+import java.lang.reflect.Array;
+
+import android.net.VpnService;
+
+public class HttpProxy extends Thread
+{
+	public static final int DEFAULT_PORT = 8080;
+	
+	private ServerSocket server = null;
+	private int thisPort = DEFAULT_PORT;
+	private String fwdServer = "";
+	private int fwdPort = 0;
+	private int ptTimeout = ProxyThread.DEFAULT_TIMEOUT;
+	private int debugLevel = 0;
+	private PrintStream debugOut = System.out;
+	
+	public static VpnService vpnService;
+	
+	/* here's a main method, in case you want to run this by itself */
+	public static void main (String args[])
+	{
+		int port = 0;
+		String fwdProxyServer = "";
+		int fwdProxyPort = 0;
+		
+		if (args.length == 0)
+		{
+			System.err.println("USAGE: java HttpProxy <port number> [<fwd proxy> <fwd port>]");
+			System.err.println("  <port number>   the port this service listens on");
+			System.err.println("  <fwd proxy>     optional proxy server to forward requests to");
+			System.err.println("  <fwd port>      the port that the optional proxy server is on");
+			System.err.println("\nHINT: if you don't want to see all the debug information flying by,");
+			System.err.println("you can pipe the output to a file or to 'nul' using \">\". For example:");
+			System.err.println("  to send output to the file prox.txt: java HttpProxy 8080 > prox.txt");
+			System.err.println("  to make the output go away: java HttpProxy 8080 > nul");
+			return;
+		}
+		
+		// get the command-line parameters
+		port = Integer.parseInt(args[0]);
+		if (args.length > 2)
+		{
+			fwdProxyServer = args[1];
+			fwdProxyPort = Integer.parseInt(args[2]);
+		}
+		
+		// create and start the HttpProxy thread, using a 20 second timeout
+		// value to keep the threads from piling up too much
+		System.err.println("  **  Starting HttpProxy on port " + port + ". Press CTRL-C to end.  **\n");
+		HttpProxy jp = new HttpProxy(port, fwdProxyServer, fwdProxyPort, 20);
+		jp.setDebug(1, System.out);		// or set the debug level to 2 for tons of output
+		jp.start();
+		
+		// run forever; if you were calling this class from another
+		// program and you wanted to stop the HttpProxy thread at some
+		// point, you could write a loop that waits for a certain
+		// condition and then calls HttpProxy.closeSocket() to kill
+		// the running HttpProxy thread
+		while (true)
+		{
+			try { Thread.sleep(3000); } catch (Exception e) {}
+		}
+		
+		// if we ever had a condition that stopped the loop above,
+		// we'd want to do this to kill the running thread
+		//jp.closeSocket();
+		//return;
+	}
+	
+	
+	/* the proxy server just listens for connections and creates
+	 * a new thread for each connection attempt (the ProxyThread
+	 * class really does all the work)
+	 */
+	public HttpProxy (int port)
+	{
+		thisPort = port;
+	}
+	
+	public HttpProxy (int port, String proxyServer, int proxyPort)
+	{
+		thisPort = port;
+		fwdServer = proxyServer;
+		fwdPort = proxyPort;
+	}
+	
+	public HttpProxy (int port, String proxyServer, int proxyPort, int timeout)
+	{
+		thisPort = port;
+		fwdServer = proxyServer;
+		fwdPort = proxyPort;
+		ptTimeout = timeout;
+	}
+	
+	public static void setVpnService (final VpnService v)
+	{
+		vpnService = v;
+	}
+	
+	/* allow the user to decide whether or not to send debug
+	 * output to the console or some other PrintStream
+	 */
+	public void setDebug (int level, PrintStream out)
+	{
+		debugLevel = level;
+		debugOut = out;
+	}
+	
+	
+	/* get the port that we're supposed to be listening on
+	 */
+	public int getPort ()
+	{
+		return thisPort;
+	}
+	 
+	
+	/* return whether or not the socket is currently open
+	 */
+	public boolean isRunning ()
+	{
+		if (server == null)
+			return false;
+		else
+			return true;
+	}
+	 
+	
+	/* closeSocket will close the open ServerSocket; use this
+	 * to halt a running HttpProxy thread
+	 */
+	public void closeSocket ()
+	{
+		try {
+			// close the open server socket
+			server.close();
+			// send it a message to make it stop waiting immediately
+			// (not really necessary)
+			/*Socket s = new Socket("localhost", thisPort);
+			OutputStream os = s.getOutputStream();
+			os.write((byte)0);
+			os.close();
+			s.close();*/
+		}  catch(Exception e)  { 
+			if (debugLevel > 0)
+				debugOut.println(e);
+		}
+		
+		server = null;
+	}
+	
+	
+	public void run()
+	{
+		try {
+			// create a server socket, and loop forever listening for
+			// client connections
+			server = new ServerSocket(thisPort);
+			if (debugLevel > 0)
+				debugOut.println("Started HttpProxy on port " + thisPort);
+			
+			while (true)
+			{
+				Socket client = server.accept();
+				HttpProxy.vpnService.protect(client);
+				ProxyThread t = new ProxyThread(client, fwdServer, fwdPort);
+				t.setDebug(debugLevel, debugOut);
+				t.setTimeout(ptTimeout);
+				t.start();
+			}
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("HttpProxy Thread error: " + e);
+		}
+		
+		closeSocket();
+	}
+	
+}
+
+
+/* 
+ * The ProxyThread will take an HTTP request from the client
+ * socket and send it to either the server that the client is
+ * trying to contact, or another proxy server
+ */
+class ProxyThread extends Thread
+{
+	private Socket pSocket;
+	private String fwdServer = "";
+	private int fwdPort = 0;
+	private int debugLevel = 0;
+	private PrintStream debugOut = System.out;
+	
+	// the socketTimeout is used to time out the connection to
+	// the remote server after a certain period of inactivity;
+	// the value is in milliseconds -- use zero if you don't want 
+	// a timeout
+	public static final int DEFAULT_TIMEOUT = 20 * 1000;
+	private int socketTimeout = DEFAULT_TIMEOUT;
+	
+	
+	public ProxyThread(Socket s)
+	{
+		pSocket = s;
+	}
+
+	public ProxyThread(Socket s, String proxy, int port)
+	{
+		pSocket = s;
+		fwdServer = proxy;
+		fwdPort = port;
+	}
+	
+	
+	public void setTimeout (int timeout)
+	{
+		// assume that the user will pass the timeout value
+		// in seconds (because that's just more intuitive)
+		socketTimeout = timeout * 1000;
+	}
+
+
+	public void setDebug (int level, PrintStream out)
+	{
+		debugLevel = level;
+		debugOut = out;
+	}
+
+
+	public void run()
+	{
+		try
+		{
+			long startTime = System.currentTimeMillis();
+			
+			// client streams (make sure you're using streams that use
+			// byte arrays, so things like GIF and JPEG files and file
+			// downloads will transfer properly)
+			BufferedInputStream clientIn = new BufferedInputStream(pSocket.getInputStream());
+			BufferedOutputStream clientOut = new BufferedOutputStream(pSocket.getOutputStream());
+			
+			// the socket to the remote server
+			Socket server = null;
+			
+			// other variables
+			byte[] request = null;
+			byte[] response = null;
+			int requestLength = 0;
+			int responseLength = 0;
+			int pos = -1;
+			StringBuffer host = new StringBuffer("");
+			String hostName = "";
+			int hostPort = 80;
+			
+			// get the header info (the web browser won't disconnect after
+			// it's sent a request, so make sure the waitForDisconnect
+			// parameter is false)
+			request = getHTTPData(clientIn, host, false);
+			requestLength = Array.getLength(request);
+			
+			// separate the host name from the host port, if necessary
+			// (like if it's "servername:8000")
+			hostName = host.toString();
+			pos = hostName.indexOf(":");
+			if (pos > 0)
+			{
+				try { hostPort = Integer.parseInt(hostName.substring(pos + 1)); 
+					}  catch (Exception e)  { }
+				hostName = hostName.substring(0, pos);
+			}
+			
+			// either forward this request to another proxy server or
+			// send it straight to the Host
+			try
+			{
+				server = SocketChannel.open().socket();
+				
+				if ((null != server) && (null != HttpProxy.vpnService)) {
+					HttpProxy.vpnService.protect(server);
+				}
+				
+				if ((fwdServer.length() > 0) && (fwdPort > 0))
+				{
+					//server = new Socket(fwdServer, fwdPort);
+					server.connect(new InetSocketAddress(fwdServer, fwdPort));
+					
+				}  else  {
+					//server = new Socket(hostName, hostPort);
+					server.connect(new InetSocketAddress(hostName, hostPort));
+					
+				}
+				
+				
+				HttpProxy.vpnService.protect(server);
+				
+			}  catch (Exception e)  {
+				// tell the client there was an error
+				String errMsg = "HTTP/1.0 500\nContent Type: text/plain\n\n" + 
+								"Error connecting to the server:\n" + e + "\n";
+				clientOut.write(errMsg.getBytes(), 0, errMsg.length());
+			}
+			
+			if (server != null)
+			{
+				server.setSoTimeout(socketTimeout);
+				BufferedInputStream serverIn = new BufferedInputStream(server.getInputStream());
+				BufferedOutputStream serverOut = new BufferedOutputStream(server.getOutputStream());
+				
+				// send the request out
+				serverOut.write(request, 0, requestLength);
+				serverOut.flush();
+				
+				// and get the response; if we're not at a debug level that
+				// requires us to return the data in the response, just stream
+				// it back to the client to save ourselves from having to
+				// create and destroy an unnecessary byte array. Also, we
+				// should set the waitForDisconnect parameter to 'true',
+				// because some servers (like Google) don't always set the
+				// Content-Length header field, so we have to listen until
+				// they decide to disconnect (or the connection times out).
+				if (debugLevel > 1)
+				{
+					response = getHTTPData(serverIn, true);
+					responseLength = Array.getLength(response);
+				}  else  {
+					responseLength = streamHTTPData(serverIn, clientOut, true);
+				}
+				
+				serverIn.close();
+				serverOut.close();
+			}
+			
+			// send the response back to the client, if we haven't already
+			if (debugLevel > 1)
+				clientOut.write(response, 0, responseLength);
+			
+			// if the user wants debug info, send them debug info; however,
+			// keep in mind that because we're using threads, the output won't
+			// necessarily be synchronous
+			if (debugLevel > 0)
+			{
+				long endTime = System.currentTimeMillis();
+				debugOut.println("Request from " + pSocket.getInetAddress().getHostAddress() + 
+									" on Port " + pSocket.getLocalPort() + 
+									" to host " + hostName + ":" + hostPort + 
+									"\n  (" + requestLength + " bytes sent, " + 
+									responseLength + " bytes returned, " + 
+									Long.toString(endTime - startTime) + " ms elapsed)");
+				debugOut.flush();
+			}
+			if (debugLevel > 1)
+			{
+				debugOut.println("REQUEST:\n" + (new String(request)));
+				debugOut.println("RESPONSE:\n" + (new String(response)));
+				debugOut.flush();
+			}
+			
+			// close all the client streams so we can listen again
+			clientOut.close();
+			clientIn.close();
+			pSocket.close();
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("Error in ProxyThread: " + e);
+			//e.printStackTrace();
+		}
+
+	}
+	
+	
+	private byte[] getHTTPData (InputStream in, boolean waitForDisconnect)
+	{
+		// get the HTTP data from an InputStream, and return it as
+		// a byte array
+		// the waitForDisconnect parameter tells us what to do in case
+		// the HTTP header doesn't specify the Content-Length of the
+		// transmission
+		StringBuffer foo = new StringBuffer("");
+		return getHTTPData(in, foo, waitForDisconnect);
+	}
+	
+	
+	private byte[] getHTTPData (InputStream in, StringBuffer host, boolean waitForDisconnect)
+	{
+		// get the HTTP data from an InputStream, and return it as
+		// a byte array, and also return the Host entry in the header,
+		// if it's specified -- note that we have to use a StringBuffer
+		// for the 'host' variable, because a String won't return any
+		// information when it's used as a parameter like that
+		ByteArrayOutputStream bs = new ByteArrayOutputStream();
+		streamHTTPData(in, bs, host, waitForDisconnect);
+		return bs.toByteArray();
+	}
+	
+
+	private int streamHTTPData (InputStream in, OutputStream out, boolean waitForDisconnect)
+	{
+		StringBuffer foo = new StringBuffer("");
+		return streamHTTPData(in, out, foo, waitForDisconnect);
+	}
+	
+	private int streamHTTPData (InputStream in, OutputStream out, 
+									StringBuffer host, boolean waitForDisconnect)
+	{
+		// get the HTTP data from an InputStream, and send it to
+		// the designated OutputStream
+		StringBuffer header = new StringBuffer("");
+		String data = "";
+		int responseCode = 200;
+		int contentLength = 0;
+		int pos = -1;
+		int byteCount = 0;
+
+		try
+		{
+			// get the first line of the header, so we know the response code
+			data = readLine(in);
+			if (data != null)
+			{
+				header.append(data + "\r\n");
+				pos = data.indexOf(" ");
+				if ((data.toLowerCase().startsWith("http")) && 
+					(pos >= 0) && (data.indexOf(" ", pos+1) >= 0))
+				{
+					String rcString = data.substring(pos+1, data.indexOf(" ", pos+1));
+					try
+					{
+						responseCode = Integer.parseInt(rcString);
+					}  catch (Exception e)  {
+						if (debugLevel > 0)
+							debugOut.println("Error parsing response code " + rcString);
+					}
+				}
+			}
+			
+			// get the rest of the header info
+			while ((data = readLine(in)) != null)
+			{
+				// the header ends at the first blank line
+				if (data.length() == 0)
+					break;
+				header.append(data + "\r\n");
+				
+				// check for the Host header
+				pos = data.toLowerCase().indexOf("host:");
+				if (pos >= 0)
+				{
+					host.setLength(0);
+					host.append(data.substring(pos + 5).trim());
+				}
+				
+				// check for the Content-Length header
+				pos = data.toLowerCase().indexOf("content-length:");
+				if (pos >= 0)
+					contentLength = Integer.parseInt(data.substring(pos + 15).trim());
+			}
+			
+			// add a blank line to terminate the header info
+			header.append("\r\n");
+			
+			// convert the header to a byte array, and write it to our stream
+			out.write(header.toString().getBytes(), 0, header.length());
+			
+			// if the header indicated that this was not a 200 response,
+			// just return what we've got if there is no Content-Length,
+			// because we may not be getting anything else
+			if ((responseCode != 200) && (contentLength == 0))
+			{
+				out.flush();
+				return header.length();
+			}
+
+			// get the body, if any; we try to use the Content-Length header to
+			// determine how much data we're supposed to be getting, because 
+			// sometimes the client/server won't disconnect after sending us
+			// information...
+			if (contentLength > 0)
+				waitForDisconnect = false;
+			
+			if ((contentLength > 0) || (waitForDisconnect))
+			{
+				try {
+					byte[] buf = new byte[4096];
+					int bytesIn = 0;
+					while ( ((byteCount < contentLength) || (waitForDisconnect)) 
+							&& ((bytesIn = in.read(buf)) >= 0) )
+					{
+						out.write(buf, 0, bytesIn);
+						byteCount += bytesIn;
+					}
+				}  catch (Exception e)  {
+					String errMsg = "Error getting HTTP body: " + e;
+					if (debugLevel > 0)
+						debugOut.println(errMsg);
+					//bs.write(errMsg.getBytes(), 0, errMsg.length());
+				}
+			}
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("Error getting HTTP data: " + e);
+		}
+		
+		//flush the OutputStream and return
+		try  {  out.flush();  }  catch (Exception e)  {}
+		return (header.length() + byteCount);
+	}
+	
+	
+	private String readLine (InputStream in)
+	{
+		// reads a line of text from an InputStream
+		StringBuffer data = new StringBuffer("");
+		int c;
+		
+		try
+		{
+			// if we have nothing to read, just return null
+			in.mark(1);
+			if (in.read() == -1)
+				return null;
+			else
+				in.reset();
+			
+			while ((c = in.read()) >= 0)
+			{
+				// check for an end-of-line character
+				if ((c == 0) || (c == 10) || (c == 13))
+					break;
+				else
+					data.append((char)c);
+			}
+		
+			// deal with the case where the end-of-line terminator is \r\n
+			if (c == 13)
+			{
+				in.mark(1);
+				if (in.read() != 10)
+					in.reset();
+			}
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("Error getting header: " + e);
+		}
+		
+		// and return what we have
+		return data.toString();
+	}
+	
+}
diff --git a/src/org/torproject/android/vpn/OrbotVpnService.java b/src/org/torproject/android/vpn/OrbotVpnService.java
index 44a2723..42a8b2b 100644
--- a/src/org/torproject/android/vpn/OrbotVpnService.java
+++ b/src/org/torproject/android/vpn/OrbotVpnService.java
@@ -52,9 +52,11 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
     private ParcelFileDescriptor mInterface;
 
     private int mSocksProxyPort = 9999;
-    private ProxyServer mProxyServer;
+    private ProxyServer mSocksProxyServer;
     private Thread mThreadProxy;
     
+    private HttpProxy mHttpProxyServer;
+    
     private final static int VPN_MTU = 1500;
     
     @Override
@@ -112,9 +114,9 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
             {
         
                 try {
-                    mProxyServer = new ProxyServer(new ServerAuthenticatorNone(null, null));
+                    mSocksProxyServer = new ProxyServer(new ServerAuthenticatorNone(null, null));
                     ProxyServer.setVpnService(OrbotVpnService.this);
-                    mProxyServer.start(mSocksProxyPort, 5, InetAddress.getLocalHost());
+                    mSocksProxyServer.start(mSocksProxyPort, 5, InetAddress.getLocalHost());
                 } catch (Exception e) {
                     Log.d(TAG,"proxy server error: " + e.getLocalizedMessage(),e);
                 }
@@ -122,6 +124,11 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
         };
         
         mThreadProxy.start();
+        
+    	mHttpProxyServer = new HttpProxy(8888);
+    	HttpProxy.setVpnService(OrbotVpnService.this);
+    	mHttpProxyServer.setDebug(5, System.out);
+    	mHttpProxyServer.start();
     }
 
     @Override
@@ -132,10 +139,16 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
     
     private void stopVPN ()
     {
-        if (mProxyServer != null){
-            mProxyServer.stop();
-            mProxyServer = null;
+        if (mSocksProxyServer != null){
+            mSocksProxyServer.stop();
+            mSocksProxyServer = null;
+        }
+        
+        if (mHttpProxyServer != null)
+        {
+        	mHttpProxyServer.closeSocket();
         }
+        
         if (mInterface != null){
             onRevoke();
             





More information about the tor-commits mailing list