/*
Tor Research Framework - easy to use tor client library/framework
Copyright (C) 2014 Dr Gareth Owen <drgowen@gmail.com>
www.ghowen.me / github.com/drgowen/tor-research-framework
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tor.examples;
import tor.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
/**
* Created by gho on 27/06/14.
*/
public class SOCKSProxy {
class SocksClient implements TorStream.TorStreamListener {
SocketChannel client;
boolean connected;
long lastData = 0;
TorStream stream;
TorCircuit circ;
InetAddress remoteAddr;
int port;
SocksClient(SocketChannel c, TorCircuit circ) throws IOException {
client = c;
client.configureBlocking(false);
lastData = System.currentTimeMillis();
this.circ = circ;
}
public void newClientData(Selector selector, SelectionKey sk) throws IOException {
if (!connected) {
ByteBuffer inbuf = ByteBuffer.allocate(16384);
if (client.read(inbuf) < 1)
return;
inbuf.flip();
//inbufinbufinbufinb.get() final DataInputStream in = new DataInputStream(Channels.newInputStream(client));
// final DataOutputStream out = new DataOutputStream(Channels.newOutputStream(client));
// read socks header
int ver = inbuf.get();
if (ver != 4) {
throw new IOException("incorrect version" + ver);
}
int cmd = inbuf.get();
// check supported command
if (cmd != 1) {
throw new IOException("incorrect version");
}
port = inbuf.getShort();
final byte ip[] = new byte[4];
// fetch IP
inbuf.get(ip);
remoteAddr = InetAddress.getByAddress(ip);
while ((inbuf.get()) != 0) ; // username
// hostname provided, not IP
if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] != 0) { // host provided
String host = "";
byte b;
while ((b = inbuf.get()) != 0) {
host += b;
}
remoteAddr = InetAddress.getByName(host);
System.out.println(host + remoteAddr);
}
stream = circ.createStream(remoteAddr.getHostAddress(), port, this);
} else {
ByteBuffer buf = ByteBuffer.allocate(4096);
int nlen = 0;
if ((nlen = client.read(buf)) == -1)
throw new IOException("disconnected");
lastData = System.currentTimeMillis();
buf.flip();
byte b[] = new byte[nlen];
buf.get(b);
stream.send(b);
}
}
@Override
public void dataArrived(TorStream s) {
try {
if (!client.isConnected())
removeClient(this);
int availBytes = s.getInputStream().available();
byte buf[] = new byte[availBytes];
s.getInputStream().read(buf);
client.write(ByteBuffer.wrap(buf));
} catch (IOException e) {
try {
//System.out.println(e);
removeClient(this);
} catch (IOException e1) {
//e1.printStackTrace();
}
//e.printStackTrace();
}
lastData = System.currentTimeMillis();
}
@Override
public void connected(TorStream s) {
ByteBuffer out = ByteBuffer.allocate(20);
out.put((byte) 0);
out.put((byte) (0x5a));
out.putShort((short) port);
out.put(remoteAddr.getAddress());
out.flip();
try {
client.write(out);
} catch (IOException e) {
try {
removeClient(this);
} catch (IOException e1) {
e1.printStackTrace();
}
System.out.println(e);
}
connected = true;
}
@Override
public void disconnected(TorStream s) {
try {
removeClient(this);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void failure(TorStream s) {
disconnected(s);
try {
removeClient(this);
} catch (IOException e) {
e.printStackTrace();
}
}
}
static HashMap<SocketChannel, SocksClient> clients = new HashMap<>();
// utility function
public SocksClient addClient(SocketChannel s, TorCircuit circ) {
SocksClient cl;
try {
cl = new SocksClient(s, circ);
} catch (IOException e) {
e.printStackTrace();
return null;
}
clients.put(s, cl);
return cl;
}
public void removeClient(SocksClient c) throws IOException {
c.client.close();
c.stream.destroy();
clients.remove(c.client);
}
long lastTimeoutCheck = 0;
public SOCKSProxy() throws IOException {
// connect through a guard
OnionRouter guard = Consensus.getConsensus().getRouterByName("southsea0");
TorSocket sock = new TorSocket(guard);
// establish a circuit
TorCircuit circ = sock.createCircuit(false);
circ.createRoute("TorLand1");
circ.waitForState(TorCircuit.STATES.READY, false);
System.out.println("READY!!");
ServerSocketChannel serverSock = ServerSocketChannel.open();
serverSock.socket().bind(new InetSocketAddress(9050));
serverSock.configureBlocking(false);
Selector select = Selector.open();
serverSock.register(select, SelectionKey.OP_ACCEPT);
int lastClients = clients.size();
// select loop
while (true) {
select.select(1000);
Set keys = select.selectedKeys();
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey k = (SelectionKey) iterator.next();
if (!k.isValid())
continue;
// new connection?
if (k.isAcceptable() && k.channel() == serverSock) {
// server socket
SocketChannel csock = serverSock.accept();
if (csock == null)
continue;
addClient(csock, circ);
csock.register(select, SelectionKey.OP_READ);
} else if (k.isReadable()) {
// new data on a client/remote socket
SocksClient cl = clients.get(k.channel());
try {
cl.newClientData(select, k);
} catch (IOException e) { // error occurred - remove client
cl.client.close();
k.cancel();
clients.remove(cl);
//System.out.println(e);
}
}
}
// client timeout check
if (System.currentTimeMillis() - lastTimeoutCheck > 15000) {
lastTimeoutCheck = System.currentTimeMillis();
Collection<SocksClient> clientsTmp = clients.values();
for (SocksClient cl : clientsTmp) {
if ((System.currentTimeMillis() - cl.lastData) > 30000L) {
cl.stream.destroy();
cl.client.close();
clients.remove(cl);
}
}
if (clients.size() != lastClients) {
System.out.println(clients.size());
lastClients = clients.size();
}
}
}
}
public static void main(String[] args) throws IOException {
new SOCKSProxy();
}
}