aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/net/sourceforge/jsocks/ProxyServer.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/net/sourceforge/jsocks/ProxyServer.java')
-rw-r--r--app/src/main/java/net/sourceforge/jsocks/ProxyServer.java591
1 files changed, 591 insertions, 0 deletions
diff --git a/app/src/main/java/net/sourceforge/jsocks/ProxyServer.java b/app/src/main/java/net/sourceforge/jsocks/ProxyServer.java
new file mode 100644
index 0000000..225149d
--- /dev/null
+++ b/app/src/main/java/net/sourceforge/jsocks/ProxyServer.java
@@ -0,0 +1,591 @@
+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;
+ }
+}