package com.runjva.sourceforge.jsocks.protocol; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /** * Datagram socket to interract through the firewall.<BR> * Can be used same way as the normal DatagramSocket. One should be carefull * though with the datagram sizes used, as additional data is present in both * incomming and outgoing datagrams. * <p> * SOCKS5 protocol allows to send host address as either: * <ul> * <li>IPV4, normal 4 byte address. (10 bytes header size) * <li>IPV6, version 6 ip address (not supported by Java as for now). 22 bytes * header size. * <li>Host name,(7+length of the host name bytes header size). * </ul> * As with other Socks equivalents, direct addresses are handled transparently, * that is data will be send directly when required by the proxy settings. * <p> * <b>NOTE:</b><br> * Unlike other SOCKS Sockets, it <b>does not</b> support proxy chaining, and * will throw an exception if proxy has a chain proxy attached. The reason for * that is not my laziness, but rather the restrictions of the SOCKSv5 protocol. * Basicaly SOCKSv5 proxy server, needs to know from which host:port datagrams * will be send for association, and returns address to which datagrams should * be send by the client, but it does not inform client from which host:port it * is going to send datagrams, in fact there is even no guarantee they will be * send at all and from the same address each time. */ public class Socks5DatagramSocket extends DatagramSocket { InetAddress relayIP; int relayPort; Socks5Proxy proxy; private boolean server_mode = false; UDPEncapsulation encapsulation; /** * Construct Datagram socket for communication over SOCKS5 proxy server. * This constructor uses default proxy, the one set with * Proxy.setDefaultProxy() method. If default proxy is not set or it is set * to version4 proxy, which does not support datagram forwarding, throws * SocksException. */ public Socks5DatagramSocket() throws SocksException, IOException { this(SocksProxyBase.defaultProxy, 0, null); } /** * Construct Datagram socket for communication over SOCKS5 proxy server. And * binds it to the specified local port. This constructor uses default * proxy, the one set with Proxy.setDefaultProxy() method. If default proxy * is not set or it is set to version4 proxy, which does not support * datagram forwarding, throws SocksException. */ public Socks5DatagramSocket(int port) throws SocksException, IOException { this(SocksProxyBase.defaultProxy, port, null); } /** * Construct Datagram socket for communication over SOCKS5 proxy server. And * binds it to the specified local port and address. This constructor uses * default proxy, the one set with Proxy.setDefaultProxy() method. If * default proxy is not set or it is set to version4 proxy, which does not * support datagram forwarding, throws SocksException. */ public Socks5DatagramSocket(int port, InetAddress ip) throws SocksException, IOException { this(SocksProxyBase.defaultProxy, port, ip); } /** * Constructs datagram socket for communication over specified proxy. And * binds it to the given local address and port. Address of null and port of * 0, signify any availabale port/address. Might throw SocksException, if: * <ol> * <li>Given version of proxy does not support UDP_ASSOCIATE. * <li>Proxy can't be reached. * <li>Authorization fails. * <li>Proxy does not want to perform udp forwarding, for any reason. * </ol> * Might throw IOException if binding datagram socket to given address/port * fails. See java.net.DatagramSocket for more details. */ public Socks5DatagramSocket(SocksProxyBase p, int port, InetAddress ip) throws SocksException, IOException { super(port, ip); if (p == null) { throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY); } if (!(p instanceof Socks5Proxy)) { final String s = "Datagram Socket needs Proxy version 5"; throw new SocksException(-1, s); } if (p.chainProxy != null) { final String s = "Datagram Sockets do not support proxy chaining."; throw new SocksException(SocksProxyBase.SOCKS_JUST_ERROR, s); } proxy = (Socks5Proxy) p.copy(); final ProxyMessage msg = proxy.udpAssociate(super.getLocalAddress(), super.getLocalPort()); relayIP = msg.ip; if (relayIP.getHostAddress().equals("0.0.0.0")) { // FIXME: What happens here? relayIP = proxy.proxyIP; } relayPort = msg.port; encapsulation = proxy.udp_encapsulation; //log.debug("Datagram Socket:{}:{}", getLocalAddress(), getLocalPort()); //log.debug("Socks5Datagram: {}:{}", relayIP, relayPort); } /** * Used by UDPRelayServer. */ Socks5DatagramSocket(boolean server_mode, UDPEncapsulation encapsulation, InetAddress relayIP, int relayPort) throws IOException { super(); this.server_mode = server_mode; this.relayIP = relayIP; this.relayPort = relayPort; this.encapsulation = encapsulation; this.proxy = null; } /** * Sends the Datagram either through the proxy or directly depending on * current proxy settings and destination address. <BR> * * <B> NOTE: </B> DatagramPacket size should be at least 10 bytes less than * the systems limit. * * <P> * See documentation on java.net.DatagramSocket for full details on how to * use this method. * * @param dp * Datagram to send. * @throws IOException * If error happens with I/O. */ public void send(DatagramPacket dp) throws IOException { // If the host should be accessed directly, send it as is. if (!server_mode && proxy.isDirect(dp.getAddress())) { super.send(dp); //log.debug("Sending datagram packet directly:"); return; } final byte[] head = formHeader(dp.getAddress(), dp.getPort()); byte[] buf = new byte[head.length + dp.getLength()]; final byte[] data = dp.getData(); // Merge head and data System.arraycopy(head, 0, buf, 0, head.length); // System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength()); System.arraycopy(data, 0, buf, head.length, dp.getLength()); if (encapsulation != null) { buf = encapsulation.udpEncapsulate(buf, true); } super.send(new DatagramPacket(buf, buf.length, relayIP, relayPort)); } /** * This method allows to send datagram packets with address type DOMAINNAME. * SOCKS5 allows to specify host as names rather than ip addresses.Using * this method one can send udp datagrams through the proxy, without having * to know the ip address of the destination host. * <p> * If proxy specified for that socket has an option resolveAddrLocally set * to true host will be resolved, and the datagram will be send with address * type IPV4, if resolve fails, UnknownHostException is thrown. * * @param dp * Datagram to send, it should contain valid port and data * @param host * Host name to which datagram should be send. * @throws IOException * If error happens with I/O, or the host can't be resolved when * proxy settings say that hosts should be resolved locally. * @see Socks5Proxy#resolveAddrLocally(boolean) */ public void send(DatagramPacket dp, String host) throws IOException { if (proxy.isDirect(host)) { dp.setAddress(InetAddress.getByName(host)); super.send(dp); return; } if ((proxy).resolveAddrLocally) { dp.setAddress(InetAddress.getByName(host)); } final byte[] head = formHeader(host, dp.getPort()); byte[] buf = new byte[head.length + dp.getLength()]; final byte[] data = dp.getData(); // Merge head and data System.arraycopy(head, 0, buf, 0, head.length); // System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength()); System.arraycopy(data, 0, buf, head.length, dp.getLength()); if (encapsulation != null) { buf = encapsulation.udpEncapsulate(buf, true); } super.send(new DatagramPacket(buf, buf.length, relayIP, relayPort)); } /** * Receives udp packet. If packet have arrived from the proxy relay server, * it is processed and address and port of the packet are set to the address * and port of sending host.<BR> * If the packet arrived from anywhere else it is not changed.<br> * <B> NOTE: </B> DatagramPacket size should be at least 10 bytes bigger * than the largest packet you expect (this is for IPV4 addresses). For * hostnames and IPV6 it is even more. * * @param dp * Datagram in which all relevent information will be copied. */ public void receive(DatagramPacket dp) throws IOException { super.receive(dp); if (server_mode) { // Drop all datagrams not from relayIP/relayPort final int init_length = dp.getLength(); final int initTimeout = getSoTimeout(); final long startTime = System.currentTimeMillis(); while (!relayIP.equals(dp.getAddress()) || (relayPort != dp.getPort())) { // Restore datagram size dp.setLength(init_length); // If there is a non-infinit timeout on this socket // Make sure that it happens no matter how often unexpected // packets arrive. if (initTimeout != 0) { final long passed = System.currentTimeMillis() - startTime; final int newTimeout = initTimeout - (int) passed; if (newTimeout <= 0) { throw new InterruptedIOException( "In Socks5DatagramSocket->receive()"); } setSoTimeout(newTimeout); } super.receive(dp); } // Restore timeout settings if (initTimeout != 0) { setSoTimeout(initTimeout); } } else if (!relayIP.equals(dp.getAddress()) || (relayPort != dp.getPort())) { return; // Recieved direct packet // If the datagram is not from the relay server, return it it as is. } byte[] data; data = dp.getData(); if (encapsulation != null) { data = encapsulation.udpEncapsulate(data, false); } // FIXME: What is this? final int offset = 0; // Java 1.1 // int offset = dp.getOffset(); //Java 1.2 final ByteArrayInputStream bIn = new ByteArrayInputStream(data, offset, dp.getLength()); final ProxyMessage msg = new Socks5Message(bIn); dp.setPort(msg.port); dp.setAddress(msg.getInetAddress()); // what wasn't read by the Message is the data final int data_length = bIn.available(); // Shift data to the left System.arraycopy(data, offset + dp.getLength() - data_length, data, offset, data_length); dp.setLength(data_length); } /** * Returns port assigned by the proxy, to which datagrams are relayed. It is * not the same port to which other party should send datagrams. * * @return Port assigned by socks server to which datagrams are send for * association. */ public int getLocalPort() { if (server_mode) { return super.getLocalPort(); } return relayPort; } /** * Address assigned by the proxy, to which datagrams are send for relay. It * is not necesseraly the same address, to which other party should send * datagrams. * * @return Address to which datagrams are send for association. */ public InetAddress getLocalAddress() { if (server_mode) { return super.getLocalAddress(); } return relayIP; } /** * Closes datagram socket, and proxy connection. */ public void close() { if (!server_mode) { proxy.endSession(); } super.close(); } /** * This method checks wether proxy still runs udp forwarding service for * this socket. * <p> * This methods checks wether the primary connection to proxy server is * active. If it is, chances are that proxy continues to forward datagrams * being send from this socket. If it was closed, most likely datagrams are * no longer being forwarded by the server. * <p> * Proxy might decide to stop forwarding datagrams, in which case it should * close primary connection. This method allows to check, wether this have * been done. * <p> * You can specify timeout for which we should be checking EOF condition on * the primary connection. Timeout is in milliseconds. Specifying 0 as * timeout implies infinity, in which case method will block, until * connection to proxy is closed or an error happens, and then return false. * <p> * One possible scenario is to call isProxyactive(0) in separate thread, and * once it returned notify other threads about this event. * * @param timeout * For how long this method should block, before returning. * @return true if connection to proxy is active, false if eof or error * condition have been encountered on the connection. */ public boolean isProxyAlive(int timeout) { if (server_mode) { return false; } if (proxy != null) { try { proxy.proxySocket.setSoTimeout(timeout); final int eof = proxy.in.read(); if (eof < 0) { return false; // EOF encountered. } else { //log.warn("This really should not happen"); return true; // This really should not happen } } catch (final InterruptedIOException iioe) { return true; // read timed out. } catch (final IOException ioe) { return false; } } return false; } // PRIVATE METHODS // //////////////// private byte[] formHeader(InetAddress ip, int port) { final Socks5Message request = new Socks5Message(0, ip, port); request.data[0] = 0; return request.data; } private byte[] formHeader(String host, int port) { final Socks5Message request = new Socks5Message(0, host, port); request.data[0] = 0; return request.data; } /* * ====================================================================== * * //Mainly Test functions ////////////////////// * * private String bytes2String(byte[] b){ String s=""; char[] hex_digit = { * '0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F'}; * for(int i=0;i<b.length;++i){ int i1 = (b[i] & 0xF0) >> 4; int i2 = b[i] & * 0xF; s+=hex_digit[i1]; s+=hex_digit[i2]; s+=" "; } return s; } private * static final void debug(String s){ if(DEBUG) System.out.print(s); } * * private static final boolean DEBUG = true; * * * public static void usage(){ System.err.print( * "Usage: java Socks.SocksDatagramSocket host port [socksHost socksPort]\n" * ); } * * static final int defaultProxyPort = 1080; //Default Port static final * String defaultProxyHost = "www-proxy"; //Default proxy * * public static void main(String args[]){ int port; String host; int * proxyPort; String proxyHost; InetAddress ip; * * if(args.length > 1 && args.length < 5){ try{ * * host = args[0]; port = Integer.parseInt(args[1]); * * proxyPort =(args.length > 3)? Integer.parseInt(args[3]) : * defaultProxyPort; * * host = args[0]; ip = InetAddress.getByName(host); * * proxyHost =(args.length > 2)? args[2] : defaultProxyHost; * * Proxy.setDefaultProxy(proxyHost,proxyPort); Proxy p = * Proxy.getDefaultProxy(); p.addDirect("lux"); * * * DatagramSocket ds = new Socks5DatagramSocket(); * * * BufferedReader in = new BufferedReader( new * InputStreamReader(System.in)); String s; * * System.out.print("Enter line:"); s = in.readLine(); * * while(s != null){ byte[] data = (s+"\r\n").getBytes(); DatagramPacket dp * = new DatagramPacket(data,0,data.length, ip,port); * System.out.println("Sending to: "+ip+":"+port); ds.send(dp); dp = new * DatagramPacket(new byte[1024],1024); * * System.out.println("Trying to recieve on port:"+ ds.getLocalPort()); * ds.receive(dp); System.out.print("Recieved:\n"+ * "From:"+dp.getAddress()+":"+dp.getPort()+ "\n\n"+ new * String(dp.getData(),dp.getOffset(),dp.getLength())+"\n" ); * System.out.print("Enter line:"); s = in.readLine(); * * } ds.close(); System.exit(1); * * }catch(SocksException s_ex){ System.err.println("SocksException:"+s_ex); * s_ex.printStackTrace(); System.exit(1); }catch(IOException io_ex){ * io_ex.printStackTrace(); System.exit(1); }catch(NumberFormatException * num_ex){ usage(); num_ex.printStackTrace(); System.exit(1); } * * }else{ usage(); } } */ }