tor-android/src/com/runjva/sourceforge/jsocks/protocol/Socks5DatagramSocket.java

486 lines
16 KiB
Java

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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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;
private Logger log = LoggerFactory.getLogger(Socks5DatagramSocket.class);
/**
* 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(); } }
*/
}