package com.runjva.sourceforge.jsocks.protocol; import java.io.IOException; import java.io.InterruptedIOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import com.runjva.sourceforge.jsocks.server.ServerAuthenticator; /** * 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 SocksProxyBase 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.info("Starting UDP relay server on {}:{}", relayIP, relayPort); //log.info("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 (final IOException ioe) { } finally { abort(); //log.info("UDP Pipe thread " + Thread.currentThread().getName() // + " stopped."); } } // Private methods // /////////////// private synchronized void abort() { if (pipe_thread1 == null) { return; } //log.info("Aborting UDP Relay Server"); remote_sock.close(); client_sock.close(); if (controlConnection != null) { try { controlConnection.close(); } catch (final IOException ioe) { } } if (master_thread != null) { master_thread.interrupt(); } pipe_thread1.interrupt(); pipe_thread2.interrupt(); pipe_thread1 = null; } private void pipe(DatagramSocket from, DatagramSocket to, boolean out) throws IOException { final byte[] data = new byte[datagramSize]; final 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 (final UnknownHostException uhe) { //log.info("Dropping datagram for unknown host"); } catch (final 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. final long timeSinceRead = System.currentTimeMillis() - lastReadTime; if (timeSinceRead >= iddleTimeout - 100) { return; } } dp.setLength(data.length); } } }