view src/net/sourceforge/jsocks/UDPRelayServer.java @ 254:5517cca2bd81

update version; help shows full version info; update change list
author Carl Byington <carl@five-ten-sg.com>
date Mon, 14 Jul 2014 12:54:57 -0700
parents 0ce5cc452d02
children 205ee2873330
line wrap: on
line source

package net.sourceforge.jsocks;
import net.sourceforge.jsocks.server.*;
import java.net.*;
import java.io.*;

/**
 UDP Relay server, used by ProxyServer to perform udp forwarding.
*/
class UDPRelayServer implements Runnable {


    DatagramSocket client_sock;
    DatagramSocket remote_sock;

    Socket controlConnection;

    int relayPort;
    InetAddress relayIP;

    Thread pipe_thread1, pipe_thread2;
    Thread master_thread;

    ServerAuthenticator auth;

    long lastReadTime;

    static PrintStream log = null;
    static Proxy proxy = null;
    static int datagramSize = 0xFFFF;//64K, a bit more than max udp size
    static int iddleTimeout = 180000;//3 minutes


    /**
      Constructs UDP relay server to communicate with client
      on given ip and port.
      @param clientIP Address of the client from whom datagrams
      will be recieved and to whom they will be forwarded.
      @param clientPort Clients port.
      @param master_thread Thread which will be interrupted, when
      UDP relay server stoppes for some reason.
      @param controlConnection Socket which will be closed, before
      interrupting the master thread, it is introduced due to a bug
      in windows JVM which does not throw InterruptedIOException in
      threads which block in I/O operation.
    */
    public UDPRelayServer(InetAddress clientIP, int clientPort,
                          Thread master_thread,
                          Socket controlConnection,
                          ServerAuthenticator auth)
    throws IOException {
        this.master_thread = master_thread;
        this.controlConnection = controlConnection;
        this.auth = auth;
        client_sock = new Socks5DatagramSocket(true, auth.getUdpEncapsulation(),
                                               clientIP, clientPort);
        relayPort = client_sock.getLocalPort();
        relayIP   = client_sock.getLocalAddress();

        if (relayIP.getHostAddress().equals("0.0.0.0"))
            relayIP   = InetAddress.getLocalHost();

        if (proxy == null)
            remote_sock = new DatagramSocket();
        else
            remote_sock = new Socks5DatagramSocket(proxy, 0, null);
    }


//Public methods
/////////////////


    /**
     Sets the timeout for UDPRelay server.<br>
     Zero timeout implies infinity.<br>
     Default timeout is 3 minutes.
     */

    static public void setTimeout(int timeout) {
        iddleTimeout = 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.
     */
    static public void setDatagramSize(int size) {
        datagramSize = size;
    }

    /**
      Port to which client should send datagram for association.
    */
    public int getRelayPort() {
        return relayPort;
    }
    /**
     IP address to which client should send datagrams for association.
    */
    public InetAddress getRelayIP() {
        return relayIP;
    }

    /**
      Starts udp relay server.
      Spawns two threads of execution and returns.
    */
    public void start() throws IOException {
        remote_sock.setSoTimeout(iddleTimeout);
        client_sock.setSoTimeout(iddleTimeout);
        log("Starting UDP relay server on " + relayIP + ":" + relayPort);
        log("Remote socket " + remote_sock.getLocalAddress() + ":" +
            remote_sock.getLocalPort());
        pipe_thread1 = new Thread(this, "pipe1");
        pipe_thread2 = new Thread(this, "pipe2");
        lastReadTime = System.currentTimeMillis();
        pipe_thread1.start();
        pipe_thread2.start();
    }

    /**
     Stops Relay server.
     <p>
     Does not close control connection, does not interrupt master_thread.
    */

    public synchronized void stop() {
        master_thread = null;
        controlConnection = null;
        abort();
    }

//Runnable interface
////////////////////
    public void run() {
        try {
            if (Thread.currentThread().getName().equals("pipe1"))
                pipe(remote_sock, client_sock, false);
            else
                pipe(client_sock, remote_sock, true);
        }
        catch (IOException ioe) {
        }
        finally {
            abort();
            log("UDP Pipe thread " + Thread.currentThread().getName() + " stopped.");
        }
    }

//Private methods
/////////////////

    private synchronized void abort() {
        if (pipe_thread1 == null) return;

        log("Aborting UDP Relay Server");
        remote_sock.close();
        client_sock.close();

        if (controlConnection != null)
            try { controlConnection.close();}
            catch (IOException ioe) {}

        if (master_thread != null) master_thread.interrupt();

        pipe_thread1.interrupt();
        pipe_thread2.interrupt();
        pipe_thread1 = null;
    }


    static private void log(String s) {
        if (log != null) {
            log.println(s);
            log.flush();
        }
    }

    private void pipe(DatagramSocket from, DatagramSocket to, boolean out)
    throws IOException {
        byte[] data = new byte[datagramSize];
        DatagramPacket dp = new DatagramPacket(data, data.length);

        while (true) {
            try {
                from.receive(dp);
                lastReadTime = System.currentTimeMillis();

                if (auth.checkRequest(dp, out))
                    to.send(dp);
            }
            catch (UnknownHostException uhe) {
                log("Dropping datagram for unknown host");
            }
            catch (InterruptedIOException iioe) {
                //log("Interrupted: "+iioe);
                //If we were interrupted by other thread.
                if (iddleTimeout == 0) return;

                //If last datagram was received, long time ago, return.
                long timeSinceRead = System.currentTimeMillis() - lastReadTime;

                if (timeSinceRead >= iddleTimeout - 100) //-100 for adjustment
                    return;
            }

            dp.setLength(data.length);
        }
    }
}