package io.nucleo.net;
import com.msopentech.thali.toronionproxy.OnionProxyContext;
import com.msopentech.thali.toronionproxy.OnionProxyManager;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.GregorianCalendar;
public abstract class TorNode<M extends OnionProxyManager, C extends OnionProxyContext> {
static final String PROXY_LOCALHOST = "127.0.0.1";
private static final int RETRY_SLEEP = 500;
private static final int TOTAL_SEC_PER_STARTUP = 4 * 60;
private static final int TRIES_PER_STARTUP = 5;
private static final Logger log = LoggerFactory.getLogger(TorNode.class);
private final OnionProxyManager tor;
private final Socks5Proxy proxy;
public TorNode(M mgr) throws IOException {
OnionProxyContext ctx = mgr.getOnionProxyContext();
log.debug("Running Tornode with " + mgr.getClass().getSimpleName() + " and " + ctx.getClass().getSimpleName());
tor = initTor(mgr, ctx);
int proxyPort = tor.getIPv4LocalHostSocksPort();
log.debug("TorSocks running on port " + proxyPort);
this.proxy = setupSocksProxy(proxyPort);
}
public Socks5Proxy getSocksProxy() {
return proxy;
}
private Socks5Proxy setupSocksProxy(int proxyPort) throws UnknownHostException {
Socks5Proxy proxy = new Socks5Proxy(PROXY_LOCALHOST, proxyPort);
proxy.resolveAddrLocally(false);
return proxy;
}
public Socket connectToHiddenService(String onionUrl, int port) throws IOException {
return connectToHiddenService(onionUrl, port, 5);
}
public Socket connectToHiddenService(String onionUrl, int port, int numTries) throws IOException {
return connectToHiddenService(onionUrl, port, numTries, true);
}
private Socket connectToHiddenService(String onionUrl, int port, int numTries, boolean debug) throws IOException {
long before = GregorianCalendar.getInstance().getTimeInMillis();
for (int i = 0; i < numTries; ++i) {
try {
SocksSocket ssock = new SocksSocket(proxy, onionUrl, port);
if (debug)
log.debug("Took " + (GregorianCalendar.getInstance().getTimeInMillis() - before)
+ " milliseconds to connect to " + onionUrl + ":" + port);
ssock.setTcpNoDelay(true);
return ssock;
} catch (UnknownHostException exx) {
try {
if (debug)
log.debug(
"Try " + (i + 1) + " connecting to " + onionUrl + ":" + port + " failed. retrying...");
Thread.sleep(RETRY_SLEEP);
continue;
} catch (InterruptedException e) {
}
} catch (Exception e) {
throw new IOException("Cannot connect to hidden service");
}
}
throw new IOException("Cannot connect to hidden service");
}
public void addHiddenServiceReadyListener(HiddenServiceDescriptor hiddenServiceDescriptor,
HiddenServiceReadyListener listener) throws IOException {
tor.attachHiddenServiceReadyListener(hiddenServiceDescriptor, listener);
}
public HiddenServiceDescriptor createHiddenService(final int localPort, final int servicePort) throws IOException {
return createHiddenService(localPort, servicePort, null);
}
public HiddenServiceDescriptor createHiddenService(final int localPort, final int servicePort,
final HiddenServiceReadyListener listener) throws IOException {
log.debug("Publishing Hidden Service. This will at least take half a minute...");
final String hiddenServiceName = tor.publishHiddenService(servicePort, localPort);
final HiddenServiceDescriptor hiddenServiceDescriptor = new HiddenServiceDescriptor(hiddenServiceName,
localPort, servicePort);
if (listener != null)
tor.attachHiddenServiceReadyListener(hiddenServiceDescriptor, listener);
return hiddenServiceDescriptor;
}
public HiddenServiceDescriptor createHiddenService(int port, HiddenServiceReadyListener listener)
throws IOException {
return createHiddenService(port, port, listener);
}
public void shutdown() throws IOException {
tor.stop();
}
static <M extends OnionProxyManager, C extends OnionProxyContext> OnionProxyManager initTor(final M mgr, C ctx)
throws IOException {
log.debug("Trying to start tor in directory {}", mgr.getWorkingDirectory());
try {
if (!mgr.startWithRepeat(TOTAL_SEC_PER_STARTUP, TRIES_PER_STARTUP)) {
throw new IOException("Could not Start Tor.");
} else {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
mgr.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
} catch (InterruptedException e) {
throw new IOException(e);
}
return mgr;
}
}