commit 096eae705baeaa05e703efbdfc0cd52b965699cb Author: Nathan Freitas nathan@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();
tor-commits@lists.torproject.org