Mercurial > 510Connectbot
diff src/net/sourceforge/jsocks/ProxyServer.java @ 0:0ce5cc452d02
initial version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 22 May 2014 10:41:19 -0700 |
parents | |
children | 205ee2873330 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/sourceforge/jsocks/ProxyServer.java Thu May 22 10:41:19 2014 -0700 @@ -0,0 +1,639 @@ +package net.sourceforge.jsocks; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PushbackInputStream; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.NoRouteToHostException; +import java.net.ServerSocket; +import java.net.Socket; + +import net.sourceforge.jsocks.server.ServerAuthenticator; + +/** + SOCKS4 and SOCKS5 proxy, handles both protocols simultaniously. + Implements all SOCKS commands, including UDP relaying. + <p> + In order to use it you will need to implement ServerAuthenticator + interface. There is an implementation of this interface which does + no authentication ServerAuthenticatorNone, but it is very dangerous + to use, as it will give access to your local network to anybody + in the world. One should never use this authentication scheme unless + one have pretty good reason to do so. + There is a couple of other authentication schemes in socks.server package. + @see socks.server.ServerAuthenticator +*/ +public class ProxyServer implements Runnable { + + ServerAuthenticator auth; + ProxyMessage msg = null; + + Socket sock = null, remote_sock = null; + ServerSocket ss = null; + UDPRelayServer relayServer = null; + InputStream in, remote_in; + OutputStream out, remote_out; + + int mode; + static final int START_MODE = 0; + static final int ACCEPT_MODE = 1; + static final int PIPE_MODE = 2; + static final int ABORT_MODE = 3; + + static final int BUF_SIZE = 8192; + + Thread pipe_thread1, pipe_thread2; + long lastReadTime; + + protected static int iddleTimeout = 180000; //3 minutes + static int acceptTimeout = 180000; //3 minutes + + static PrintStream log = null; + static Proxy proxy; + + +//Public Constructors +///////////////////// + + + /** + Creates a proxy server with given Authentication scheme. + @param auth Authentication scheme to be used. + */ + public ProxyServer(ServerAuthenticator auth) { + this.auth = auth; + } + +//Other constructors +//////////////////// + + protected ProxyServer(ServerAuthenticator auth, Socket s) { + this.auth = auth; + this.sock = s; + mode = START_MODE; + } + +//Public methods +///////////////// + + /** + Set the logging stream. Specifying null disables logging. + */ + public static void setLog(OutputStream out) { + if (out == null) { + log = null; + } + else { + log = new PrintStream(out, true); + } + + UDPRelayServer.log = log; + } + + /** + Set proxy. + <p> + Allows Proxy chaining so that one Proxy server is connected to another + and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests + can be handled, UDP would not work, however CONNECT and BIND will be + translated. + + @param p Proxy which should be used to handle user requests. + */ + public static void setProxy(Proxy p) { + proxy = p; + UDPRelayServer.proxy = proxy; + } + + /** + Get proxy. + @return Proxy wich is used to handle user requests. + */ + public static Proxy getProxy() { + return proxy; + } + + /** + Sets the timeout for connections, how long shoud server wait + for data to arrive before dropping the connection.<br> + Zero timeout implies infinity.<br> + Default timeout is 3 minutes. + */ + public static void setIddleTimeout(int timeout) { + iddleTimeout = timeout; + } + /** + Sets the timeout for BIND command, how long the server should + wait for the incoming connection.<br> + Zero timeout implies infinity.<br> + Default timeout is 3 minutes. + */ + public static void setAcceptTimeout(int timeout) { + acceptTimeout = timeout; + } + + /** + Sets the timeout for UDPRelay server.<br> + Zero timeout implies infinity.<br> + Default timeout is 3 minutes. + */ + public static void setUDPTimeout(int timeout) { + UDPRelayServer.setTimeout(timeout); + } + + /** + Sets the size of the datagrams used in the UDPRelayServer.<br> + Default size is 64K, a bit more than maximum possible size of the + datagram. + */ + public static void setDatagramSize(int size) { + UDPRelayServer.setDatagramSize(size); + } + + + /** + Start the Proxy server at given port.<br> + This methods blocks. + */ + public void start(int port) { + start(port, 5, null); + } + + /** + Create a server with the specified port, listen backlog, and local + IP address to bind to. The localIP argument can be used on a multi-homed + host for a ServerSocket that will only accept connect requests to one of + its addresses. If localIP is null, it will default accepting connections + on any/all local addresses. The port must be between 0 and 65535, + inclusive. <br> + This methods blocks. + */ + public void start(int port, int backlog, InetAddress localIP) { + try { + ss = new ServerSocket(port, backlog, localIP); + log("Starting SOCKS Proxy on:" + ss.getInetAddress().getHostAddress() + ":" + + ss.getLocalPort()); + + while (true) { + Socket s = ss.accept(); + log("Accepted from:" + s.getInetAddress().getHostName() + ":" + + s.getPort()); + ProxyServer ps = new ProxyServer(auth, s); + (new Thread(ps)).start(); + } + } + catch (IOException ioe) { + ioe.printStackTrace(); + } + finally { + } + } + + /** + Stop server operation.It would be wise to interrupt thread running the + server afterwards. + */ + public void stop() { + try { + if (ss != null) ss.close(); + } + catch (IOException ioe) { + } + } + +//Runnable interface +//////////////////// + public void run() { + switch (mode) { + case START_MODE: + try { + startSession(); + } + catch (IOException ioe) { + handleException(ioe); + //ioe.printStackTrace(); + } + finally { + abort(); + + if (auth != null) auth.endSession(); + + log("Main thread(client->remote)stopped."); + } + + break; + + case ACCEPT_MODE: + try { + doAccept(); + mode = PIPE_MODE; + pipe_thread1.interrupt(); //Tell other thread that connection have + //been accepted. + pipe(remote_in, out); + } + catch (IOException ioe) { + //log("Accept exception:"+ioe); + handleException(ioe); + } + finally { + abort(); + log("Accept thread(remote->client) stopped"); + } + + break; + + case PIPE_MODE: + try { + pipe(remote_in, out); + } + catch (IOException ioe) { + } + finally { + abort(); + log("Support thread(remote->client) stopped"); + } + + break; + + case ABORT_MODE: + break; + + default: + log("Unexpected MODE " + mode); + } + } + +//Private methods +///////////////// + private void startSession() throws IOException { + sock.setSoTimeout(iddleTimeout); + + try { + auth = auth.startSession(sock); + } + catch (IOException ioe) { + log("Auth throwed exception:" + ioe); + auth = null; + return; + } + + if (auth == null) { //Authentication failed + log("Authentication failed"); + return; + } + + in = auth.getInputStream(); + out = auth.getOutputStream(); + msg = readMsg(in); + handleRequest(msg); + } + + protected void handleRequest(ProxyMessage msg) + throws IOException { + if (!auth.checkRequest(msg)) throw new + SocksException(Proxy.SOCKS_FAILURE); + + if (msg.ip == null) { + if (msg instanceof Socks5Message) { + msg.ip = InetAddress.getByName(msg.host); + } + else + throw new SocksException(Proxy.SOCKS_FAILURE); + } + + log(msg); + + switch (msg.command) { + case Proxy.SOCKS_CMD_CONNECT: + onConnect(msg); + break; + + case Proxy.SOCKS_CMD_BIND: + onBind(msg); + break; + + case Proxy.SOCKS_CMD_UDP_ASSOCIATE: + onUDP(msg); + break; + + default: + throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED); + } + } + + private void handleException(IOException ioe) { + //If we couldn't read the request, return; + if (msg == null) return; + + //If have been aborted by other thread + if (mode == ABORT_MODE) return; + + //If the request was successfully completed, but exception happened later + if (mode == PIPE_MODE) return; + + int error_code = Proxy.SOCKS_FAILURE; + + if (ioe instanceof SocksException) + error_code = ((SocksException)ioe).errCode; + else if (ioe instanceof NoRouteToHostException) + error_code = Proxy.SOCKS_HOST_UNREACHABLE; + else if (ioe instanceof ConnectException) + error_code = Proxy.SOCKS_CONNECTION_REFUSED; + else if (ioe instanceof InterruptedIOException) + error_code = Proxy.SOCKS_TTL_EXPIRE; + + if (error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED || error_code < 0) { + error_code = Proxy.SOCKS_FAILURE; + } + + sendErrorMessage(error_code); + } + + private void onConnect(ProxyMessage msg) throws IOException { + Socket s; + ProxyMessage response = null; + s = new Socket(msg.ip, msg.port); + log("Connected to " + s.getInetAddress() + ":" + s.getPort()); + + if (msg instanceof Socks5Message) { + response = new Socks5Message(Proxy.SOCKS_SUCCESS, + s.getLocalAddress(), + s.getLocalPort()); + } + else { + response = new Socks4Message(Socks4Message.REPLY_OK, + s.getLocalAddress(), s.getLocalPort()); + } + + response.write(out); + startPipe(s); + } + + private void onBind(ProxyMessage msg) throws IOException { + ProxyMessage response = null; + + if (proxy == null) + ss = new ServerSocket(0); + else + ss = new SocksServerSocket(proxy, msg.ip, msg.port); + + ss.setSoTimeout(acceptTimeout); + log("Trying accept on " + ss.getInetAddress() + ":" + ss.getLocalPort()); + + if (msg.version == 5) + response = new Socks5Message(Proxy.SOCKS_SUCCESS, ss.getInetAddress(), + ss.getLocalPort()); + else + response = new Socks4Message(Socks4Message.REPLY_OK, + ss.getInetAddress(), + ss.getLocalPort()); + + response.write(out); + mode = ACCEPT_MODE; + pipe_thread1 = Thread.currentThread(); + pipe_thread2 = new Thread(this); + pipe_thread2.start(); + //Make timeout infinit. + sock.setSoTimeout(0); + int eof = 0; + + try { + while ((eof = in.read()) >= 0) { + if (mode != ACCEPT_MODE) { + if (mode != PIPE_MODE) return; //Accept failed + + remote_out.write(eof); + break; + } + } + } + catch (EOFException eofe) { + //System.out.println("EOF exception"); + return;//Connection closed while we were trying to accept. + } + catch (InterruptedIOException iioe) { + //Accept thread interrupted us. + //System.out.println("Interrupted"); + if (mode != PIPE_MODE) + return;//If accept thread was not successfull return. + } + finally { + //System.out.println("Finnaly!"); + } + + if (eof < 0) //Connection closed while we were trying to accept; + return; + + //Do not restore timeout, instead timeout is set on the + //remote socket. It does not make any difference. + pipe(in, remote_out); + } + + private void onUDP(ProxyMessage msg) throws IOException { + if (msg.ip.getHostAddress().equals("0.0.0.0")) + msg.ip = sock.getInetAddress(); + + log("Creating UDP relay server for " + msg.ip + ":" + msg.port); + relayServer = new UDPRelayServer(msg.ip, msg.port, + Thread.currentThread(), sock, auth); + ProxyMessage response; + response = new Socks5Message(Proxy.SOCKS_SUCCESS, + relayServer.relayIP, relayServer.relayPort); + response.write(out); + relayServer.start(); + //Make timeout infinit. + sock.setSoTimeout(0); + + try { + while (in.read() >= 0) /*do nothing*/; + } + catch (EOFException eofe) { + } + } + +//Private methods +////////////////// + + private void doAccept() throws IOException { + Socket s; + long startTime = System.currentTimeMillis(); + + while (true) { + s = ss.accept(); + + if (s.getInetAddress().equals(msg.ip)) { + //got the connection from the right host + //Close listenning socket. + ss.close(); + break; + } + else if (ss instanceof SocksServerSocket) { + //We can't accept more then one connection + s.close(); + ss.close(); + throw new SocksException(Proxy.SOCKS_FAILURE); + } + else { + if (acceptTimeout != 0) { //If timeout is not infinit + int newTimeout = acceptTimeout - (int)(System.currentTimeMillis() - + startTime); + + if (newTimeout <= 0) throw new InterruptedIOException( + "In doAccept()"); + + ss.setSoTimeout(newTimeout); + } + + s.close(); //Drop all connections from other hosts + } + } + + //Accepted connection + remote_sock = s; + remote_in = s.getInputStream(); + remote_out = s.getOutputStream(); + //Set timeout + remote_sock.setSoTimeout(iddleTimeout); + log("Accepted from " + s.getInetAddress() + ":" + s.getPort()); + ProxyMessage response; + + if (msg.version == 5) + response = new Socks5Message(Proxy.SOCKS_SUCCESS, s.getInetAddress(), + s.getPort()); + else + response = new Socks4Message(Socks4Message.REPLY_OK, + s.getInetAddress(), s.getPort()); + + response.write(out); + } + + protected ProxyMessage readMsg(InputStream in) throws IOException { + PushbackInputStream push_in; + + if (in instanceof PushbackInputStream) + push_in = (PushbackInputStream) in; + else + push_in = new PushbackInputStream(in); + + int version = push_in.read(); + push_in.unread(version); + ProxyMessage msg; + + if (version == 5) { + msg = new Socks5Message(push_in, false); + } + else if (version == 4) { + msg = new Socks4Message(push_in, false); + } + else { + throw new SocksException(Proxy.SOCKS_FAILURE); + } + + return msg; + } + + private void startPipe(Socket s) { + mode = PIPE_MODE; + remote_sock = s; + + try { + remote_in = s.getInputStream(); + remote_out = s.getOutputStream(); + pipe_thread1 = Thread.currentThread(); + pipe_thread2 = new Thread(this); + pipe_thread2.start(); + pipe(in, remote_out); + } + catch (IOException ioe) { + } + } + + private void sendErrorMessage(int error_code) { + ProxyMessage err_msg; + + if (msg instanceof Socks4Message) + err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED); + else + err_msg = new Socks5Message(error_code); + + try { + err_msg.write(out); + } + catch (IOException ioe) {} + } + + private synchronized void abort() { + if (mode == ABORT_MODE) return; + + mode = ABORT_MODE; + + try { + log("Aborting operation"); + + if (remote_sock != null) remote_sock.close(); + + if (sock != null) sock.close(); + + if (relayServer != null) relayServer.stop(); + + if (ss != null) ss.close(); + + if (pipe_thread1 != null) pipe_thread1.interrupt(); + + if (pipe_thread2 != null) pipe_thread2.interrupt(); + } + catch (IOException ioe) {} + } + + static final void log(String s) { + if (log != null) { + log.println(s); + log.flush(); + } + } + + static final void log(ProxyMessage msg) { + log("Request version:" + msg.version + + "\tCommand: " + command2String(msg.command)); + log("IP:" + msg.ip + "\tPort:" + msg.port + + (msg.version == 4 ? "\tUser:" + msg.user : "")); + } + + private void pipe(InputStream in, OutputStream out) throws IOException { + lastReadTime = System.currentTimeMillis(); + byte[] buf = new byte[BUF_SIZE]; + int len = 0; + + while (len >= 0) { + try { + if (len != 0) { + out.write(buf, 0, len); + out.flush(); + } + + len = in.read(buf); + lastReadTime = System.currentTimeMillis(); + } + catch (InterruptedIOException iioe) { + if (iddleTimeout == 0) return; //Other thread interrupted us. + + long timeSinceRead = System.currentTimeMillis() - lastReadTime; + + if (timeSinceRead >= iddleTimeout - 1000) //-1s for adjustment. + return; + + len = 0; + } + } + } + static final String command_names[] = {"CONNECT", "BIND", "UDP_ASSOCIATE"}; + + static final String command2String(int cmd) { + if (cmd > 0 && cmd < 4) return command_names[cmd - 1]; + else return "Unknown Command " + cmd; + } +}