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.
    Zero timeout implies infinity.
    Default timeout is 3 minutes.
    */
    static public void setTimeout(int timeout){
      iddleTimeout = timeout;
    }
   /**
     Sets the size of the datagrams used in the UDPRelayServer.
     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.
     
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); } } }