mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-20 23:56:30 -04:00
Update to master of jtorproxy
This commit is contained in:
parent
cefcf5b3d8
commit
9b1108aa0a
@ -13,7 +13,9 @@ See the Apache 2 License for the specific language governing permissions and lim
|
||||
|
||||
package com.msopentech.thali.java.toronionproxy;
|
||||
|
||||
import com.msopentech.thali.toronionproxy.FileUtilities;
|
||||
import com.msopentech.thali.toronionproxy.OnionProxyContext;
|
||||
import com.msopentech.thali.toronionproxy.OsData;
|
||||
import com.msopentech.thali.toronionproxy.WriteObserver;
|
||||
|
||||
import java.io.File;
|
||||
@ -42,8 +44,61 @@ public class JavaOnionProxyContext extends OnionProxyContext {
|
||||
|
||||
@Override
|
||||
public String getProcessId() {
|
||||
// This is a horrible hack. It seems like more JVMs will return the process's PID this way, but not guarantees.
|
||||
// This is a horrible hack. It seems like more JVMs will return the
|
||||
// process's PID this way, but not guarantees.
|
||||
String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
|
||||
return processName.split("@")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installFiles() throws IOException, InterruptedException {
|
||||
super.installFiles();
|
||||
switch (OsData.getOsType()) {
|
||||
case Windows:
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
case Mac:
|
||||
FileUtilities.extractContentFromZip(getWorkingDirectory(),
|
||||
getAssetOrResourceByName(getPathToTorExecutable() + "tor.zip"));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS yet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathToTorExecutable() {
|
||||
String path = "native/";
|
||||
switch (OsData.getOsType()) {
|
||||
case Windows:
|
||||
return path + "windows/x86/"; // We currently only support the
|
||||
// x86 build but that should work
|
||||
// everywhere
|
||||
case Mac:
|
||||
return path + "osx/x64/"; // I don't think there even is a x32
|
||||
// build of Tor for Mac, but could be
|
||||
// wrong.
|
||||
case Linux32:
|
||||
return path + "linux/x86/";
|
||||
case Linux64:
|
||||
return path + "linux/x64/";
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTorExecutableFileName() {
|
||||
switch (OsData.getOsType()) {
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
return "tor";
|
||||
case Windows:
|
||||
return "tor.exe";
|
||||
case Mac:
|
||||
return "tor.real";
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,7 @@ public class FileUtilities {
|
||||
|
||||
/**
|
||||
* Closes both input and output streams when done.
|
||||
*
|
||||
* @param in Stream to read from
|
||||
* @param in Stream to read from
|
||||
* @param out Stream to write to
|
||||
* @throws java.io.IOException - If close on input or output fails
|
||||
*/
|
||||
@ -40,8 +39,7 @@ public class FileUtilities {
|
||||
|
||||
/**
|
||||
* Won't close the input stream when it's done, needed to handle ZipInputStreams
|
||||
*
|
||||
* @param in Won't be closed
|
||||
* @param in Won't be closed
|
||||
* @param out Will be closed
|
||||
* @throws java.io.IOException - If close on output fails
|
||||
*/
|
||||
@ -86,8 +84,7 @@ public class FileUtilities {
|
||||
|
||||
/**
|
||||
* Reads the input stream, deletes fileToWriteTo if it exists and over writes it with the stream.
|
||||
*
|
||||
* @param readFrom Stream to read from
|
||||
* @param readFrom Stream to read from
|
||||
* @param fileToWriteTo File to write to
|
||||
* @throws java.io.IOException - If any of the file operations fail
|
||||
*/
|
||||
@ -113,9 +110,8 @@ public class FileUtilities {
|
||||
|
||||
/**
|
||||
* This has to exist somewhere! Why isn't it a part of the standard Java library?
|
||||
*
|
||||
* @param destinationDirectory Directory files are to be extracted to
|
||||
* @param zipFileInputStream Stream to unzip
|
||||
* @param zipFileInputStream Stream to unzip
|
||||
* @throws java.io.IOException - If there are any file errors
|
||||
*/
|
||||
public static void extractContentFromZip(File destinationDirectory, InputStream zipFileInputStream)
|
||||
|
@ -44,11 +44,10 @@ abstract public class OnionProxyContext {
|
||||
torrcFile = new File(getWorkingDirectory(), torrcName);
|
||||
torExecutableFile = new File(getWorkingDirectory(), getTorExecutableFileName());
|
||||
cookieFile = new File(getWorkingDirectory(), ".tor/control_auth_cookie");
|
||||
hostnameFile = new File(getWorkingDirectory(), "/" + hiddenserviceDirectoryName
|
||||
+ "/hostname");
|
||||
hostnameFile = new File(getWorkingDirectory(), "/" + hiddenserviceDirectoryName + "/hostname");
|
||||
}
|
||||
|
||||
public void installFiles() throws IOException, InterruptedException {
|
||||
protected void installFiles() throws IOException, InterruptedException {
|
||||
// This is sleezy but we have cases where an old instance of the Tor OP
|
||||
// needs an extra second to
|
||||
// clean itself up. Without that time we can't do things like delete its
|
||||
@ -56,7 +55,13 @@ abstract public class OnionProxyContext {
|
||||
// do by default, something we hope to fix with
|
||||
// https://github.com/thaliproject/Tor_Onion_Proxy_Library/issues/13
|
||||
Thread.sleep(1000, 0);
|
||||
|
||||
if (getWorkingDirectory().listFiles() != null) {
|
||||
for (File f : getWorkingDirectory().listFiles()) {
|
||||
if (f.getAbsolutePath().startsWith(torrcFile.getAbsolutePath())) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
File dotTorDir = new File(getWorkingDirectory(), ".tor");
|
||||
if (dotTorDir.exists())
|
||||
@ -70,30 +75,15 @@ abstract public class OnionProxyContext {
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(geoIpName), geoIpFile);
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(geoIpv6Name), geoIpv6File);
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(torrcName), torrcFile);
|
||||
|
||||
switch (OsData.getOsType()) {
|
||||
case Android:
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(getPathToTorExecutable()
|
||||
+ getTorExecutableFileName()), torExecutableFile);
|
||||
break;
|
||||
case Windows:
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
case Mac:
|
||||
FileUtilities.extractContentFromZip(getWorkingDirectory(),
|
||||
getAssetOrResourceByName(getPathToTorExecutable() + "tor.zip"));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS yet");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets environment variables and working directory needed for Tor
|
||||
*
|
||||
* @param processBuilder we will call start on this to run Tor
|
||||
* @param processBuilder
|
||||
* we will call start on this to run Tor
|
||||
*/
|
||||
public void setEnvironmentArgsAndWorkingDirectoryForStart(ProcessBuilder processBuilder) {
|
||||
void setEnvironmentArgsAndWorkingDirectoryForStart(ProcessBuilder processBuilder) {
|
||||
processBuilder.directory(getWorkingDirectory());
|
||||
Map<String, String> environment = processBuilder.environment();
|
||||
environment.put("HOME", getWorkingDirectory().getAbsolutePath());
|
||||
@ -153,7 +143,7 @@ abstract public class OnionProxyContext {
|
||||
return workingDirectory;
|
||||
}
|
||||
|
||||
public void deleteAllFilesButHiddenServices() throws InterruptedException {
|
||||
void deleteAllFilesButHiddenServices() throws InterruptedException {
|
||||
// It can take a little bit for the Tor OP to detect the connection is
|
||||
// dead and kill itself
|
||||
Thread.sleep(1000);
|
||||
@ -174,45 +164,12 @@ abstract public class OnionProxyContext {
|
||||
* Files we pull out of the AAR or JAR are typically at the root but for
|
||||
* executables outside of Android the executable for a particular platform
|
||||
* is in a specific sub-directory.
|
||||
*
|
||||
*
|
||||
* @return Path to executable in JAR Resources
|
||||
*/
|
||||
protected String getPathToTorExecutable() {
|
||||
String path = "native/";
|
||||
switch (OsData.getOsType()) {
|
||||
case Android:
|
||||
return "";
|
||||
case Windows:
|
||||
return path + "windows/x86/"; // We currently only support the
|
||||
// x86 build but that should work
|
||||
// everywhere
|
||||
case Mac:
|
||||
return path + "osx/x64/"; // I don't think there even is a x32
|
||||
// build of Tor for Mac, but could be
|
||||
// wrong.
|
||||
case Linux32:
|
||||
return path + "linux/x86/";
|
||||
case Linux64:
|
||||
return path + "linux/x64/";
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS");
|
||||
}
|
||||
}
|
||||
protected abstract String getPathToTorExecutable();
|
||||
|
||||
protected String getTorExecutableFileName() {
|
||||
switch (OsData.getOsType()) {
|
||||
case Android:
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
return "tor";
|
||||
case Windows:
|
||||
return "tor.exe";
|
||||
case Mac:
|
||||
return "tor.real";
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS");
|
||||
}
|
||||
}
|
||||
protected abstract String getTorExecutableFileName();
|
||||
|
||||
abstract public String getProcessId();
|
||||
|
||||
|
@ -29,6 +29,8 @@ See the Apache 2 License for the specific language governing permissions and lim
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import io.nucleo.net.HiddenServiceDescriptor;
|
||||
import io.nucleo.net.HiddenServiceReadyListener;
|
||||
import net.freehaven.tor.control.ConfigEntry;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
import org.slf4j.Logger;
|
||||
@ -44,19 +46,20 @@ import java.util.concurrent.CountDownLatch;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* This is where all the fun is, this is the class that handles the heavy work. Note that you will most likely need
|
||||
* to actually call into the AndroidOnionProxyManager or JavaOnionProxyManager in order to create the right bindings
|
||||
* for your environment.
|
||||
* <p>
|
||||
* This class is thread safe but that's mostly because we hit everything over the head with 'synchronized'. Given the
|
||||
* way this class is used there shouldn't be any performance implications of this.
|
||||
* <p>
|
||||
* This is where all the fun is, this is the class that handles the heavy work.
|
||||
* Note that you will most likely need to actually call into the
|
||||
* AndroidOnionProxyManager or JavaOnionProxyManager in order to create the
|
||||
* right bindings for your environment.
|
||||
* <p/>
|
||||
* This class is thread safe but that's mostly because we hit everything over
|
||||
* the head with 'synchronized'. Given the way this class is used there
|
||||
* shouldn't be any performance implications of this.
|
||||
* <p/>
|
||||
* This class began life as TorPlugin from the Briar Project
|
||||
*/
|
||||
public abstract class OnionProxyManager {
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC", "ORCONN", "NOTICE", "WARN", "ERR"
|
||||
};
|
||||
private static final String[] EVENTS = {"CIRC", "WARN", "ERR"};
|
||||
private static final String[] EVENTS_HS = {"EXTENDED", "CIRC", "ORCONN", "INFO", "NOTICE", "WARN", "ERR", "HS_DESC"};
|
||||
|
||||
private static final String OWNER = "__OwningControllerProcess";
|
||||
private static final int COOKIE_TIMEOUT = 3 * 1000; // Milliseconds
|
||||
@ -65,32 +68,50 @@ public abstract class OnionProxyManager {
|
||||
|
||||
protected final OnionProxyContext onionProxyContext;
|
||||
|
||||
public OnionProxyContext getOnionProxyContext() {
|
||||
return onionProxyContext;
|
||||
}
|
||||
|
||||
private volatile Socket controlSocket = null;
|
||||
|
||||
// If controlConnection is not null then this means that a connection exists and the Tor OP will die when
|
||||
// If controlConnection is not null then this means that a connection exists
|
||||
// and the Tor OP will die when
|
||||
// the connection fails.
|
||||
private volatile TorControlConnection controlConnection = null;
|
||||
private volatile int control_port;
|
||||
|
||||
private OnionProxyManagerEventHandler eventHandler;
|
||||
|
||||
public OnionProxyManager(OnionProxyContext onionProxyContext) {
|
||||
this.onionProxyContext = onionProxyContext;
|
||||
eventHandler = new OnionProxyManagerEventHandler();
|
||||
}
|
||||
|
||||
public void attachHiddenServiceReadyListener(HiddenServiceDescriptor hs, HiddenServiceReadyListener listener) {
|
||||
eventHandler.setHStoWatchFor(hs, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a blocking call that will try to start the Tor OP, connect it to the network and get it to be fully
|
||||
* bootstrapped. Sometimes the bootstrap process just hangs for no apparent reason so the method will wait for the
|
||||
* given time for bootstrap to finish and if it doesn't then will restart the bootstrap process the given number of
|
||||
* repeats.
|
||||
* This is a blocking call that will try to start the Tor OP, connect it to
|
||||
* the network and get it to be fully bootstrapped. Sometimes the bootstrap
|
||||
* process just hangs for no apparent reason so the method will wait for the
|
||||
* given time for bootstrap to finish and if it doesn't then will restart
|
||||
* the bootstrap process the given number of repeats.
|
||||
*
|
||||
* @param secondsBeforeTimeOut Seconds to wait for boot strapping to finish
|
||||
* @param numberOfRetries Number of times to try recycling the Tor OP before giving up on bootstrapping working
|
||||
* @return True if bootstrap succeeded, false if there is a problem or the bootstrap couldn't complete in the given
|
||||
* time.
|
||||
* @throws java.lang.InterruptedException - You know, if we are interrupted
|
||||
* @throws java.io.IOException - IO Exceptions
|
||||
* @param secondsBeforeTimeOut
|
||||
* Seconds to wait for boot strapping to finish
|
||||
* @param numberOfRetries
|
||||
* Number of times to try recycling the Tor OP before giving up
|
||||
* on bootstrapping working
|
||||
* @return True if bootstrap succeeded, false if there is a problem or the
|
||||
* bootstrap couldn't complete in the given time.
|
||||
* @throws java.lang.InterruptedException
|
||||
* - You know, if we are interrupted
|
||||
* @throws java.io.IOException
|
||||
* - IO Exceptions
|
||||
*/
|
||||
public synchronized boolean startWithRepeat(int secondsBeforeTimeOut, int numberOfRetries) throws
|
||||
InterruptedException, IOException {
|
||||
public synchronized boolean startWithRepeat(int secondsBeforeTimeOut, int numberOfRetries)
|
||||
throws InterruptedException, IOException {
|
||||
if (secondsBeforeTimeOut <= 0 || numberOfRetries < 0) {
|
||||
throw new IllegalArgumentException("secondsBeforeTimeOut >= 0 & numberOfRetries > 0");
|
||||
}
|
||||
@ -102,7 +123,8 @@ public abstract class OnionProxyManager {
|
||||
}
|
||||
enableNetwork(true);
|
||||
|
||||
// We will check every second to see if boot strapping has finally finished
|
||||
// We will check every second to see if boot strapping has
|
||||
// finally finished
|
||||
for (int secondsWaited = 0; secondsWaited < secondsBeforeTimeOut; ++secondsWaited) {
|
||||
if (isBootstrapped() == false) {
|
||||
Thread.sleep(1000, 0);
|
||||
@ -113,18 +135,24 @@ public abstract class OnionProxyManager {
|
||||
|
||||
// Bootstrapping isn't over so we need to restart and try again
|
||||
stop();
|
||||
// Experimentally we have found that if a Tor OP has run before and thus has cached descriptors
|
||||
// and that when we try to start it again it won't start then deleting the cached data can fix this.
|
||||
// But, if there is cached data and things do work then the Tor OP will start faster than it would
|
||||
|
||||
// Experimentally we have found that if a Tor OP has run before and thus
|
||||
// has cached descriptors
|
||||
// and that when we try to start it again it won't start then deleting
|
||||
// the cached data can fix this.
|
||||
// But, if there is cached data and things do work then the Tor OP will
|
||||
// start faster than it would
|
||||
// if we delete everything.
|
||||
// So our compromise is that we try to start the Tor OP 'as is' on the first round and after that
|
||||
// So our compromise is that we try to start the Tor OP 'as is' on the
|
||||
// first round and after that
|
||||
// we delete all the files.
|
||||
onionProxyContext.deleteAllFilesButHiddenServices();
|
||||
}
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
// Make sure we return the Tor OP in some kind of consistent state, even if it's 'off'.
|
||||
// Make sure we return the Tor OP in some kind of consistent state,
|
||||
// even if it's 'off'.
|
||||
if (isRunning() == false) {
|
||||
stop();
|
||||
}
|
||||
@ -132,22 +160,26 @@ public abstract class OnionProxyManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the socks port on the IPv4 localhost address that the Tor OP is listening on
|
||||
* Returns the socks port on the IPv4 localhost address that the Tor OP is
|
||||
* listening on
|
||||
*
|
||||
* @return Discovered socks port
|
||||
* @throws java.io.IOException - File errors
|
||||
* @throws java.io.IOException
|
||||
* - File errors
|
||||
*/
|
||||
public synchronized int getIPv4LocalHostSocksPort() throws IOException {
|
||||
if (isRunning() == false) {
|
||||
throw new RuntimeException("Tor is not running!");
|
||||
}
|
||||
|
||||
// This returns a set of space delimited quoted strings which could be Ipv4, Ipv6 or unix sockets
|
||||
// This returns a set of space delimited quoted strings which could be
|
||||
// Ipv4, Ipv6 or unix sockets
|
||||
String[] socksIpPorts = controlConnection.getInfo("net/listeners/socks").split(" ");
|
||||
|
||||
for (String address : socksIpPorts) {
|
||||
if (address.contains("\"127.0.0.1:")) {
|
||||
// Remember, the last character will be a " so we have to remove that
|
||||
// Remember, the last character will be a " so we have to remove
|
||||
// that
|
||||
return Integer.parseInt(address.substring(address.lastIndexOf(":") + 1, address.length() - 1));
|
||||
}
|
||||
}
|
||||
@ -158,10 +190,14 @@ public abstract class OnionProxyManager {
|
||||
/**
|
||||
* Publishes a hidden service
|
||||
*
|
||||
* @param hiddenServicePort The port that the hidden service will accept connections on
|
||||
* @param localPort The local port that the hidden service will relay connections to
|
||||
* @param hiddenServicePort
|
||||
* The port that the hidden service will accept connections on
|
||||
* @param localPort
|
||||
* The local port that the hidden service will relay connections
|
||||
* to
|
||||
* @return The hidden service's onion address in the form X.onion.
|
||||
* @throws java.io.IOException - File errors
|
||||
* @throws java.io.IOException
|
||||
* - File errors
|
||||
*/
|
||||
public synchronized String publishHiddenService(int hiddenServicePort, int localPort) throws IOException {
|
||||
if (controlConnection == null) {
|
||||
@ -170,20 +206,19 @@ public abstract class OnionProxyManager {
|
||||
|
||||
List<ConfigEntry> currentHiddenServices = controlConnection.getConf("HiddenServiceOptions");
|
||||
|
||||
if ((currentHiddenServices.size() == 1 &&
|
||||
currentHiddenServices.get(0).key.compareTo("HiddenServiceOptions") == 0 &&
|
||||
currentHiddenServices.get(0).value.compareTo("") == 0) == false) {
|
||||
throw new RuntimeException("Sorry, only one hidden service to a customer and we already have one. Please " +
|
||||
"send complaints to https://github" +
|
||||
".com/thaliproject/Tor_Onion_Proxy_Library/issues/5 with your scenario so we can justify fixing " +
|
||||
"this.");
|
||||
if ((currentHiddenServices.size() == 1
|
||||
&& currentHiddenServices.get(0).key.compareTo("HiddenServiceOptions") == 0
|
||||
&& currentHiddenServices.get(0).value.compareTo("") == 0) == false) {
|
||||
throw new RuntimeException("Sorry, only one hidden service to a customer and we already have one. Please "
|
||||
+ "send complaints to https://github"
|
||||
+ ".com/thaliproject/Tor_Onion_Proxy_Library/issues/5 with your scenario so we can justify fixing "
|
||||
+ "this.");
|
||||
}
|
||||
|
||||
LOG.info("Creating hidden service");
|
||||
File hostnameFile = onionProxyContext.getHostNameFile();
|
||||
|
||||
if (hostnameFile.getParentFile().exists() == false &&
|
||||
hostnameFile.getParentFile().mkdirs() == false) {
|
||||
if (hostnameFile.getParentFile().exists() == false && hostnameFile.getParentFile().mkdirs() == false) {
|
||||
throw new RuntimeException("Could not create hostnameFile parent directory");
|
||||
}
|
||||
|
||||
@ -193,16 +228,14 @@ public abstract class OnionProxyManager {
|
||||
// Thanks, Ubuntu!
|
||||
try {
|
||||
switch (OsData.getOsType()) {
|
||||
case Mac:
|
||||
case Android:
|
||||
case Linux32:
|
||||
case Linux64: {
|
||||
case Linux64:
|
||||
case Mac: {
|
||||
Set<PosixFilePermission> perms = new HashSet<>();
|
||||
perms.add(PosixFilePermission.OWNER_READ);
|
||||
perms.add(PosixFilePermission.OWNER_WRITE);
|
||||
perms.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(onionProxyContext.getHiddenServiceDirectory()
|
||||
.toPath(), perms);
|
||||
Files.setPosixFilePermissions(onionProxyContext.getHiddenServiceDirectory().toPath(), perms);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@ -211,11 +244,12 @@ public abstract class OnionProxyManager {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
controlConnection.setEvents(Arrays.asList(EVENTS_HS));
|
||||
// Watch for the hostname file being created/updated
|
||||
WriteObserver hostNameFileObserver = onionProxyContext.generateWriteObserver(hostnameFile);
|
||||
// Use the control connection to update the Tor config
|
||||
List<String> config = Arrays.asList(
|
||||
"HiddenServiceDir " + hostnameFile.getParentFile().getAbsolutePath(),
|
||||
List<String> config = Arrays.asList("HiddenServiceDir " + hostnameFile.getParentFile().getAbsolutePath(),
|
||||
"HiddenServicePort " + hiddenServicePort + " 127.0.0.1:" + localPort);
|
||||
controlConnection.setConf(config);
|
||||
controlConnection.saveConf();
|
||||
@ -232,11 +266,24 @@ public abstract class OnionProxyManager {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public synchronized boolean isHiddenServiceAvailable(String onionurl) {
|
||||
try {
|
||||
return controlConnection.isHSAvailable(onionurl.substring(0, onionurl.indexOf(".")));
|
||||
} catch (IOException e) {
|
||||
// We'll have to wait for tor 0.2.7
|
||||
e.printStackTrace();
|
||||
System.err.println("We'll have to wait for Tor 0.2.7 for HSFETCH to work!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the Tor OP Process. Once you have called this method nothing is going to work until you either call
|
||||
* startWithRepeat or installAndStartTorOp
|
||||
* Kills the Tor OP Process. Once you have called this method nothing is
|
||||
* going to work until you either call startWithRepeat or
|
||||
* installAndStartTorOp
|
||||
*
|
||||
* @throws java.io.IOException - File errors
|
||||
* @throws java.io.IOException
|
||||
* - File errors
|
||||
*/
|
||||
public synchronized void stop() throws IOException {
|
||||
try {
|
||||
@ -256,10 +303,12 @@ public abstract class OnionProxyManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the Tor OP is running (e.g. fully bootstrapped) and open to network connections.
|
||||
* Checks to see if the Tor OP is running (e.g. fully bootstrapped) and open
|
||||
* to network connections.
|
||||
*
|
||||
* @return True if running
|
||||
* @throws java.io.IOException - IO exceptions
|
||||
* @throws java.io.IOException
|
||||
* - IO exceptions
|
||||
*/
|
||||
public synchronized boolean isRunning() throws IOException {
|
||||
return isBootstrapped() && isNetworkEnabled();
|
||||
@ -268,8 +317,11 @@ public abstract class OnionProxyManager {
|
||||
/**
|
||||
* Tells the Tor OP if it should accept network connections
|
||||
*
|
||||
* @param enable If true then the Tor OP will accept SOCKS connections, otherwise not.
|
||||
* @throws java.io.IOException - IO exceptions
|
||||
* @param enable
|
||||
* If true then the Tor OP will accept SOCKS connections,
|
||||
* otherwise not.
|
||||
* @throws java.io.IOException
|
||||
* - IO exceptions
|
||||
*/
|
||||
public synchronized void enableNetwork(boolean enable) throws IOException {
|
||||
if (controlConnection == null) {
|
||||
@ -282,9 +334,10 @@ public abstract class OnionProxyManager {
|
||||
/**
|
||||
* Specifies if Tor OP is accepting network connections
|
||||
*
|
||||
* @return True if network is enabled (that doesn't mean that the device is online, only that the Tor OP is trying
|
||||
* to connect to the network)
|
||||
* @throws java.io.IOException - IO exceptions
|
||||
* @return True if network is enabled (that doesn't mean that the device is
|
||||
* online, only that the Tor OP is trying to connect to the network)
|
||||
* @throws java.io.IOException
|
||||
* - IO exceptions
|
||||
*/
|
||||
public synchronized boolean isNetworkEnabled() throws IOException {
|
||||
if (controlConnection == null) {
|
||||
@ -293,7 +346,8 @@ public abstract class OnionProxyManager {
|
||||
|
||||
List<ConfigEntry> disableNetworkSettingValues = controlConnection.getConf("DisableNetwork");
|
||||
boolean result = false;
|
||||
// It's theoretically possible for us to get multiple values back, if even one is false then we will
|
||||
// It's theoretically possible for us to get multiple values back, if
|
||||
// even one is false then we will
|
||||
// assume all are false
|
||||
for (ConfigEntry configEntry : disableNetworkSettingValues) {
|
||||
if (configEntry.value.equals("1")) {
|
||||
@ -323,7 +377,7 @@ public abstract class OnionProxyManager {
|
||||
}
|
||||
|
||||
if (phase != null && phase.contains("PROGRESS=100")) {
|
||||
LOG.trace("Tor has already bootstrapped");
|
||||
LOG.info("Tor has already bootstrapped");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -331,23 +385,29 @@ public abstract class OnionProxyManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs all necessary files and starts the Tor OP in offline mode (e.g. networkEnabled(false)). This would
|
||||
* only be used if you wanted to start the Tor OP so that the install and related is all done but aren't ready to
|
||||
* actually connect it to the network.
|
||||
* Installs all necessary files and starts the Tor OP in offline mode (e.g.
|
||||
* networkEnabled(false)). This would only be used if you wanted to start
|
||||
* the Tor OP so that the install and related is all done but aren't ready
|
||||
* to actually connect it to the network.
|
||||
*
|
||||
* @return True if all files installed and Tor OP successfully started
|
||||
* @throws java.io.IOException - IO Exceptions
|
||||
* @throws java.lang.InterruptedException - If we are, well, interrupted
|
||||
* @throws java.io.IOException
|
||||
* - IO Exceptions
|
||||
* @throws java.lang.InterruptedException
|
||||
* - If we are, well, interrupted
|
||||
*/
|
||||
public synchronized boolean installAndStartTorOp() throws IOException, InterruptedException {
|
||||
// The Tor OP will die if it looses the connection to its socket so if there is no controlSocket defined
|
||||
// then Tor is dead. This assumes, of course, that takeOwnership works and we can't end up with Zombies.
|
||||
// The Tor OP will die if it looses the connection to its socket so if
|
||||
// there is no controlSocket defined
|
||||
// then Tor is dead. This assumes, of course, that takeOwnership works
|
||||
// and we can't end up with Zombies.
|
||||
if (controlConnection != null) {
|
||||
LOG.info("Tor is already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
// The code below is why this method is synchronized, we don't want two instances of it running at once
|
||||
// The code below is why this method is synchronized, we don't want two
|
||||
// instances of it running at once
|
||||
// as the result would be a mess of screwed up files and connections.
|
||||
LOG.info("Tor is not running");
|
||||
|
||||
@ -355,13 +415,14 @@ public abstract class OnionProxyManager {
|
||||
|
||||
LOG.info("Starting Tor");
|
||||
File cookieFile = onionProxyContext.getCookieFile();
|
||||
if (cookieFile.getParentFile().exists() == false &&
|
||||
cookieFile.getParentFile().mkdirs() == false) {
|
||||
if (cookieFile.getParentFile().exists() == false && cookieFile.getParentFile().mkdirs() == false) {
|
||||
throw new RuntimeException("Could not create cookieFile parent directory");
|
||||
}
|
||||
|
||||
// The original code from Briar watches individual files, not a directory and Android's file observer
|
||||
// won't work on files that don't exist. Rather than take 5 seconds to rewrite Briar's code I instead
|
||||
// The original code from Briar watches individual files, not a
|
||||
// directory and Android's file observer
|
||||
// won't work on files that don't exist. Rather than take 5 seconds to
|
||||
// rewrite Briar's code I instead
|
||||
// just make sure the file exists
|
||||
if (cookieFile.exists() == false && cookieFile.createNewFile() == false) {
|
||||
throw new RuntimeException("Could not create cookieFile");
|
||||
@ -379,18 +440,25 @@ public abstract class OnionProxyManager {
|
||||
onionProxyContext.setEnvironmentArgsAndWorkingDirectoryForStart(processBuilder);
|
||||
Process torProcess = null;
|
||||
try {
|
||||
// torProcess = Runtime.getRuntime().exec(cmd, env, workingDirectory);
|
||||
// torProcess = Runtime.getRuntime().exec(cmd, env,
|
||||
// workingDirectory);
|
||||
torProcess = processBuilder.start();
|
||||
CountDownLatch controlPortCountDownLatch = new CountDownLatch(1);
|
||||
eatStream(torProcess.getInputStream(), false, controlPortCountDownLatch);
|
||||
eatStream(torProcess.getErrorStream(), true, null);
|
||||
|
||||
// On platforms other than Windows we run as a daemon and so we need to wait for the process to detach
|
||||
// or exit. In the case of Windows the equivalent is running as a service and unfortunately that requires
|
||||
// managing the service, such as turning it off or uninstalling it when it's time to move on. Any number
|
||||
// of errors can prevent us from doing the cleanup and so we would leave the process running around. Rather
|
||||
// than do that on Windows we just let the process run on the exec and hence don't look for an exit code.
|
||||
// This does create a condition where the process has exited due to a problem but we should hopefully
|
||||
// On platforms other than Windows we run as a daemon and so we need
|
||||
// to wait for the process to detach
|
||||
// or exit. In the case of Windows the equivalent is running as a
|
||||
// service and unfortunately that requires
|
||||
// managing the service, such as turning it off or uninstalling it
|
||||
// when it's time to move on. Any number
|
||||
// of errors can prevent us from doing the cleanup and so we would
|
||||
// leave the process running around. Rather
|
||||
// than do that on Windows we just let the process run on the exec
|
||||
// and hence don't look for an exit code.
|
||||
// This does create a condition where the process has exited due to
|
||||
// a problem but we should hopefully
|
||||
// detect that when we try to use the control connection.
|
||||
if (OsData.getOsType() != OsData.OsType.Windows) {
|
||||
int exit = torProcess.waitFor();
|
||||
@ -418,11 +486,12 @@ public abstract class OnionProxyManager {
|
||||
// Tell Tor to exit when the control connection is closed
|
||||
controlConnection.takeOwnership();
|
||||
controlConnection.resetConf(Collections.singletonList(OWNER));
|
||||
// Register to receive events from the Tor process
|
||||
controlConnection.setEventHandler(new OnionProxyManagerEventHandler());
|
||||
|
||||
controlConnection.setEventHandler(eventHandler);
|
||||
controlConnection.setEvents(Arrays.asList(EVENTS));
|
||||
|
||||
// We only set the class property once the connection is in a known good state
|
||||
// We only set the class property once the connection is in a known
|
||||
// good state
|
||||
this.controlConnection = controlConnection;
|
||||
return true;
|
||||
} catch (SecurityException e) {
|
||||
@ -434,8 +503,10 @@ public abstract class OnionProxyManager {
|
||||
return false;
|
||||
} finally {
|
||||
if (controlConnection == null && torProcess != null) {
|
||||
// It's possible that something 'bad' could happen after we executed exec but before we takeOwnership()
|
||||
// in which case the Tor OP will hang out as a zombie until this process is killed. This is problematic
|
||||
// It's possible that something 'bad' could happen after we
|
||||
// executed exec but before we takeOwnership()
|
||||
// in which case the Tor OP will hang out as a zombie until this
|
||||
// process is killed. This is problematic
|
||||
// when we want to do things like
|
||||
torProcess.destroy();
|
||||
}
|
||||
@ -443,8 +514,8 @@ public abstract class OnionProxyManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root directory in which the Tor Onion Proxy keeps its files. This is mostly intended
|
||||
* for debugging purposes.
|
||||
* Returns the root directory in which the Tor Onion Proxy keeps its files.
|
||||
* This is mostly intended for debugging purposes.
|
||||
*
|
||||
* @return Working directory for Tor Onion Proxy files
|
||||
*/
|
||||
@ -452,7 +523,8 @@ public abstract class OnionProxyManager {
|
||||
return onionProxyContext.getWorkingDirectory();
|
||||
}
|
||||
|
||||
protected void eatStream(final InputStream inputStream, final boolean stdError, final CountDownLatch countDownLatch) {
|
||||
protected void eatStream(final InputStream inputStream, final boolean stdError,
|
||||
final CountDownLatch countDownLatch) {
|
||||
new Thread("eatStream") {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -464,14 +536,16 @@ public abstract class OnionProxyManager {
|
||||
LOG.error(scanner.nextLine());
|
||||
} else {
|
||||
String nextLine = scanner.nextLine();
|
||||
// We need to find the line where it tells us what the control port is.
|
||||
// The line that will appear in stdio with the control port looks like:
|
||||
// We need to find the line where it tells us what
|
||||
// the control port is.
|
||||
// The line that will appear in stdio with the
|
||||
// control port looks like:
|
||||
// Control listener listening on port 39717.
|
||||
if (nextLine.contains("Control listener listening on port ")) {
|
||||
// For the record, I hate regex so I'm doing this manually
|
||||
control_port =
|
||||
Integer.parseInt(
|
||||
nextLine.substring(nextLine.lastIndexOf(" ") + 1, nextLine.length() - 1));
|
||||
// For the record, I hate regex so I'm doing
|
||||
// this manually
|
||||
control_port = Integer.parseInt(
|
||||
nextLine.substring(nextLine.lastIndexOf(" ") + 1, nextLine.length() - 1));
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
LOG.info(nextLine);
|
||||
@ -497,15 +571,19 @@ public abstract class OnionProxyManager {
|
||||
throw new RuntimeException("could not make Tor executable.");
|
||||
}
|
||||
|
||||
// We need to edit the config file to specify exactly where the cookie/geoip files should be stored, on
|
||||
// Android this is always a fixed location relative to the configFiles which is why this extra step
|
||||
// wasn't needed in Briar's Android code. But in Windows it ends up in the user's AppData/Roaming. Rather
|
||||
// We need to edit the config file to specify exactly where the
|
||||
// cookie/geoip files should be stored, on
|
||||
// Android this is always a fixed location relative to the configFiles
|
||||
// which is why this extra step
|
||||
// wasn't needed in Briar's Android code. But in Windows it ends up in
|
||||
// the user's AppData/Roaming. Rather
|
||||
// than track it down we just tell Tor where to put it.
|
||||
PrintWriter printWriter = null;
|
||||
try {
|
||||
printWriter = new PrintWriter(new BufferedWriter(new FileWriter(onionProxyContext.getTorrcFile(), true)));
|
||||
printWriter.println("CookieAuthFile " + onionProxyContext.getCookieFile().getAbsolutePath());
|
||||
// For some reason the GeoIP's location can only be given as a file name, not a path and it has
|
||||
// For some reason the GeoIP's location can only be given as a file
|
||||
// name, not a path and it has
|
||||
// to be in the data directory so we need to set both
|
||||
printWriter.println("DataDirectory " + onionProxyContext.getWorkingDirectory().getAbsolutePath());
|
||||
printWriter.println("GeoIPFile " + onionProxyContext.getGeoIpFile().getName());
|
||||
@ -520,7 +598,8 @@ public abstract class OnionProxyManager {
|
||||
/**
|
||||
* Alas old versions of Android do not support setExecutable.
|
||||
*
|
||||
* @param f File to make executable
|
||||
* @param f
|
||||
* File to make executable
|
||||
* @return True if it worked, otherwise false.
|
||||
*/
|
||||
protected abstract boolean setExecutable(File f);
|
||||
|
@ -29,6 +29,8 @@ limitations under the License.
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import io.nucleo.net.HiddenServiceDescriptor;
|
||||
import io.nucleo.net.HiddenServiceReadyListener;
|
||||
import net.freehaven.tor.control.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -42,26 +44,43 @@ import java.util.List;
|
||||
*/
|
||||
public class OnionProxyManagerEventHandler implements EventHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OnionProxyManagerEventHandler.class);
|
||||
private HiddenServiceDescriptor hs;
|
||||
private HiddenServiceReadyListener listener;
|
||||
private boolean hsPublished;
|
||||
|
||||
public void setHStoWatchFor(HiddenServiceDescriptor hs, HiddenServiceReadyListener listener) {
|
||||
if (hs == this.hs && hsPublished) {
|
||||
listener.onConnect(hs);
|
||||
return;
|
||||
}
|
||||
this.listener = listener;
|
||||
this.hs = hs;
|
||||
hsPublished = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void circuitStatus(String status, String id, String path) {
|
||||
String msg = "CircuitStatus: " + id + " " + status + ", " + path;
|
||||
LOG.info(msg);
|
||||
LOG.debug(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamStatus(String status, String id, String target) {
|
||||
LOG.info("streamStatus: status: " + status + ", id: " + id + ", target: " + target);
|
||||
final String msg = "streamStatus: status: " + status + ", id: " + id + ", target: " + target;
|
||||
LOG.debug(msg);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
LOG.info("OR connection: status: " + status + ", orName: " + orName);
|
||||
final String msg = "OR connection: status: " + status + ", orName: " + orName;
|
||||
LOG.debug(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bandwidthUsed(long read, long written) {
|
||||
LOG.info("bandwidthUsed: read: " + read + ", written: " + written);
|
||||
LOG.debug("bandwidthUsed: read: " + read + ", written: " + written);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -71,17 +90,33 @@ public class OnionProxyManagerEventHandler implements EventHandler {
|
||||
while (iterator.hasNext()) {
|
||||
stringBuilder.append(iterator.next());
|
||||
}
|
||||
LOG.info("newDescriptors: " + stringBuilder.toString());
|
||||
final String msg = "newDescriptors: " + stringBuilder.toString();
|
||||
LOG.debug(msg);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void message(String severity, String msg) {
|
||||
LOG.info("message: severity: " + severity + ", msg: " + msg);
|
||||
final String msg2 = "message: severity: " + severity + ", msg: " + msg;
|
||||
LOG.debug(msg2);
|
||||
if (severity.equalsIgnoreCase("INFO"))
|
||||
checkforHS(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unrecognized(String type, String msg) {
|
||||
LOG.info("unrecognized: type: " + type + ", msg: " + msg);
|
||||
final String msg2 = "unrecognized: type: " + type + ", msg: " + msg;
|
||||
LOG.debug(msg2);
|
||||
}
|
||||
|
||||
private void checkforHS(String msg) {
|
||||
if (hs == null || hsPublished == true)
|
||||
return;
|
||||
String pattern = "uploading rendezvous descriptor";
|
||||
if (msg.toLowerCase().contains(pattern)) {
|
||||
hsPublished = true;
|
||||
LOG.info("Hidden service " + hs.getFullAddress() + " published.");
|
||||
listener.onConnect(hs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ import java.util.Scanner;
|
||||
|
||||
public class OsData {
|
||||
public enum OsType {Windows, Linux32, Linux64, Mac, Android}
|
||||
|
||||
private static OsType detectedType = null;
|
||||
|
||||
public static OsType getOsType() {
|
||||
@ -51,7 +50,7 @@ public class OsData {
|
||||
protected static OsType actualGetOsType() {
|
||||
|
||||
//This also works for ART
|
||||
if (System.getProperty("java.vm.name").contains("Dalvik")) {
|
||||
if (System.getProperty("java.vm.name").contains("Dalvik")) {
|
||||
return OsType.Android;
|
||||
}
|
||||
|
||||
@ -87,7 +86,7 @@ public class OsData {
|
||||
throw new RuntimeException("Uname returned error code " + exit);
|
||||
}
|
||||
|
||||
if (unameOutput.compareTo("i686") == 0) {
|
||||
if (unameOutput.matches("i.86")) {
|
||||
return OsType.Linux32;
|
||||
}
|
||||
if (unameOutput.compareTo("x86_64") == 0) {
|
||||
|
@ -22,9 +22,8 @@ import java.util.concurrent.TimeUnit;
|
||||
public interface WriteObserver {
|
||||
/**
|
||||
* Waits timeout of unit to see if file is modified
|
||||
*
|
||||
* @param timeout How long to wait before returning
|
||||
* @param unit Unit to wait in
|
||||
* @param unit Unit to wait in
|
||||
* @return True if file was modified, false if it was not
|
||||
*/
|
||||
boolean poll(long timeout, TimeUnit unit);
|
||||
|
@ -18,212 +18,212 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class Connection implements Closeable {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Connection.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(Connection.class);
|
||||
|
||||
private final Socket socket;
|
||||
private final ObjectOutputStream out;
|
||||
private final ObjectInputStream in;
|
||||
private final LinkedList<ConnectionListener> connectionListeners;
|
||||
private final String peer;
|
||||
private boolean running;
|
||||
private final AtomicBoolean available;
|
||||
private final AtomicBoolean listening;
|
||||
private final Socket socket;
|
||||
private final ObjectOutputStream out;
|
||||
private final ObjectInputStream in;
|
||||
private final LinkedList<ConnectionListener> connectionListeners;
|
||||
private final String peer;
|
||||
private boolean running;
|
||||
private final AtomicBoolean available;
|
||||
private final AtomicBoolean listening;
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final InputStreamListener inputStreamListener;
|
||||
private final ExecutorService executorService;
|
||||
private final InputStreamListener inputStreamListener;
|
||||
|
||||
private final AtomicBoolean heartBeating;
|
||||
private final AtomicBoolean heartBeating;
|
||||
|
||||
public Connection(String peer, Socket socket) throws IOException {
|
||||
this(peer, socket, Node.prepareOOSForSocket(socket), new ObjectInputStream(socket.getInputStream()));
|
||||
public Connection(String peer, Socket socket) throws IOException {
|
||||
this(peer, socket, Node.prepareOOSForSocket(socket), new ObjectInputStream(socket.getInputStream()));
|
||||
}
|
||||
|
||||
Connection(String peer, Socket socket, ObjectOutputStream out, ObjectInputStream in) {
|
||||
log.debug("Initiating new connection");
|
||||
this.available = new AtomicBoolean(false);
|
||||
this.peer = peer;
|
||||
this.socket = socket;
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
running = true;
|
||||
listening = new AtomicBoolean(false);
|
||||
heartBeating = new AtomicBoolean(false);
|
||||
this.connectionListeners = new LinkedList<>();
|
||||
this.inputStreamListener = new InputStreamListener();
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
public abstract boolean isIncoming();
|
||||
|
||||
public void addMessageListener(ConnectionListener listener) {
|
||||
synchronized (connectionListeners) {
|
||||
connectionListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
Connection(String peer, Socket socket, ObjectOutputStream out, ObjectInputStream in) {
|
||||
log.debug("Initiating new connection");
|
||||
this.available = new AtomicBoolean(false);
|
||||
this.peer = peer;
|
||||
this.socket = socket;
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
running = true;
|
||||
listening = new AtomicBoolean(false);
|
||||
heartBeating = new AtomicBoolean(false);
|
||||
this.connectionListeners = new LinkedList<>();
|
||||
this.inputStreamListener = new InputStreamListener();
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
protected void setConnectionListeners(Collection<ConnectionListener> listeners) {
|
||||
synchronized (listeners) {
|
||||
this.connectionListeners.clear();
|
||||
this.connectionListeners.addAll(listeners);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean isIncoming();
|
||||
public void removeMessageListener(ConnectionListener listener) {
|
||||
synchronized (connectionListeners) {
|
||||
connectionListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void addMessageListener(ConnectionListener listener) {
|
||||
synchronized (connectionListeners) {
|
||||
connectionListeners.add(listener);
|
||||
void sendMsg(Message msg) throws IOException {
|
||||
out.writeObject(msg);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void sendMessage(ContainerMessage msg) throws IOException {
|
||||
if (!available.get())
|
||||
throw new IOException("Connection is not yet available!");
|
||||
sendMsg(msg);
|
||||
}
|
||||
|
||||
protected void onMessage(Message msg) throws IOException {
|
||||
log.debug("RXD: " + msg.toString());
|
||||
if (msg instanceof ContainerMessage) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners)
|
||||
l.onMessage(this, (ContainerMessage) msg);
|
||||
}
|
||||
} else {
|
||||
if (msg instanceof ControlMessage) {
|
||||
switch ((ControlMessage) msg) {
|
||||
case DISCONNECT:
|
||||
close(false, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, true));
|
||||
break;
|
||||
case AVAILABLE:
|
||||
startHeartbeat();
|
||||
onReady();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setConnectionListeners(Collection<ConnectionListener> listeners) {
|
||||
synchronized (listeners) {
|
||||
this.connectionListeners.clear();
|
||||
this.connectionListeners.addAll(listeners);
|
||||
protected void onReady() {
|
||||
if (!available.getAndSet(true)) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onReady(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMessageListener(ConnectionListener listener) {
|
||||
synchronized (connectionListeners) {
|
||||
connectionListeners.remove(listener);
|
||||
}
|
||||
protected abstract void onDisconnect();
|
||||
|
||||
private void onDisconn(DisconnectReason reason) {
|
||||
onDisconnect();
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onDisconnect(this, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendMsg(Message msg) throws IOException {
|
||||
out.writeObject(msg);
|
||||
out.flush();
|
||||
private void onTimeout() {
|
||||
try {
|
||||
close(false, PredefinedDisconnectReason.TIMEOUT);
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(ContainerMessage msg) throws IOException {
|
||||
if (!available.get())
|
||||
throw new IOException("Connection is not yet available!");
|
||||
sendMsg(msg);
|
||||
protected void onError(Exception e) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onError(this, new ConnectionException(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMessage(Message msg) throws IOException {
|
||||
log.debug("onMessage: " + msg.toString());
|
||||
if (msg instanceof ContainerMessage) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners)
|
||||
l.onMessage(this, (ContainerMessage) msg);
|
||||
}
|
||||
} else {
|
||||
if (msg instanceof ControlMessage) {
|
||||
switch ((ControlMessage) msg) {
|
||||
case DISCONNECT:
|
||||
close(false, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, true));
|
||||
break;
|
||||
case AVAILABLE:
|
||||
startHeartbeat();
|
||||
onReady();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void close() throws IOException {
|
||||
close(true, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, false));
|
||||
}
|
||||
|
||||
private void close(boolean graceful, DisconnectReason reason) throws IOException {
|
||||
running = false;
|
||||
onDisconn(reason);
|
||||
if (graceful) {
|
||||
try {
|
||||
sendMsg(ControlMessage.DISCONNECT);
|
||||
} catch (Exception e) {
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
socket.close();
|
||||
|
||||
protected void onReady() {
|
||||
if (!available.getAndSet(true)) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onReady(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onDisconnect();
|
||||
public String getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
private void onDisconn(DisconnectReason reason) {
|
||||
onDisconnect();
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onDisconnect(this, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTimeout() {
|
||||
try {
|
||||
close(false, PredefinedDisconnectReason.TIMEOUT);
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
|
||||
protected void onError(Exception e) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onError(this, new ConnectionException(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
close(true, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, false));
|
||||
}
|
||||
|
||||
private void close(boolean graceful, DisconnectReason reason) throws IOException {
|
||||
running = false;
|
||||
onDisconn(reason);
|
||||
if (graceful) {
|
||||
try {
|
||||
sendMsg(ControlMessage.DISCONNECT);
|
||||
} catch (Exception e) {
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
socket.close();
|
||||
|
||||
}
|
||||
|
||||
public String getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
void startHeartbeat() {
|
||||
if (!heartBeating.getAndSet(true)) {
|
||||
log.debug("Starting Heartbeat");
|
||||
executorService.submit(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(30000);
|
||||
while (running) {
|
||||
try {
|
||||
log.debug("TX Heartbeat");
|
||||
sendMsg(ControlMessage.HEARTBEAT);
|
||||
Thread.sleep(30000);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void listen() throws ConnectionException {
|
||||
if (listening.getAndSet(true))
|
||||
throw new ConnectionException("Already Listening!");
|
||||
executorService.submit(inputStreamListener);
|
||||
}
|
||||
|
||||
private class InputStreamListener implements Runnable {
|
||||
@Override
|
||||
void startHeartbeat() {
|
||||
if (!heartBeating.getAndSet(true)) {
|
||||
log.debug("Starting Heartbeat");
|
||||
executorService.submit(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(30000);
|
||||
while (running) {
|
||||
try {
|
||||
Message msg = (Message) in.readObject();
|
||||
onMessage(msg);
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
if (e instanceof SocketTimeoutException) {
|
||||
onTimeout();
|
||||
} else {
|
||||
if (running) {
|
||||
onError(new ConnectionException(e));
|
||||
// TODO: Fault Tolerance?
|
||||
if (e instanceof EOFException) {
|
||||
try {
|
||||
close(false, PredefinedDisconnectReason.RESET);
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
log.debug("TX Heartbeat");
|
||||
sendMsg(ControlMessage.HEARTBEAT);
|
||||
Thread.sleep(30000);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void listen() throws ConnectionException {
|
||||
if (listening.getAndSet(true))
|
||||
throw new ConnectionException("Already Listening!");
|
||||
executorService.submit(inputStreamListener);
|
||||
}
|
||||
|
||||
private class InputStreamListener implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
try {
|
||||
Message msg = (Message) in.readObject();
|
||||
onMessage(msg);
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
if (e instanceof SocketTimeoutException) {
|
||||
onTimeout();
|
||||
} else {
|
||||
if (running) {
|
||||
onError(new ConnectionException(e));
|
||||
// TODO: Fault Tolerance?
|
||||
if (e instanceof EOFException) {
|
||||
try {
|
||||
close(false, PredefinedDisconnectReason.RESET);
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import io.nucleo.net.proto.exceptions.ConnectionException;
|
||||
|
||||
public interface ConnectionListener {
|
||||
|
||||
public abstract void onMessage(Connection con, ContainerMessage msg);
|
||||
public abstract void onMessage(Connection con, ContainerMessage msg);
|
||||
|
||||
public void onDisconnect(Connection con, DisconnectReason reason);
|
||||
public void onDisconnect(Connection con, DisconnectReason reason);
|
||||
|
||||
public void onError(Connection con, ConnectionException e);
|
||||
public void onError(Connection con, ConnectionException e);
|
||||
|
||||
public void onReady(Connection con);
|
||||
public void onReady(Connection con);
|
||||
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ package io.nucleo.net;
|
||||
|
||||
public interface DisconnectReason {
|
||||
|
||||
public abstract String toString();
|
||||
public abstract String toString();
|
||||
|
||||
public boolean isGraceful();
|
||||
public boolean isGraceful();
|
||||
|
||||
public boolean isRemote();
|
||||
public boolean isRemote();
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package io.nucleo.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class HiddenServiceDescriptor extends ServiceDescriptor {
|
||||
@ -11,7 +10,7 @@ public class HiddenServiceDescriptor extends ServiceDescriptor {
|
||||
public HiddenServiceDescriptor(String serviceName, int localPort, int servicePort) throws IOException {
|
||||
super(serviceName, servicePort);
|
||||
this.localPort = localPort;
|
||||
this.serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), localPort));
|
||||
this.serverSocket.bind(new InetSocketAddress(TorNode.PROXY_LOCALHOST, localPort));
|
||||
}
|
||||
|
||||
public int getLocalPort() {
|
||||
|
@ -0,0 +1,7 @@
|
||||
package io.nucleo.net;
|
||||
|
||||
public interface HiddenServiceReadyListener {
|
||||
|
||||
public void onConnect(HiddenServiceDescriptor descriptor);
|
||||
|
||||
}
|
15
jtorproxy/src/main/java/io/nucleo/net/JavaTorNode.java
Normal file
15
jtorproxy/src/main/java/io/nucleo/net/JavaTorNode.java
Normal file
@ -0,0 +1,15 @@
|
||||
package io.nucleo.net;
|
||||
|
||||
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext;
|
||||
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class JavaTorNode extends TorNode<JavaOnionProxyManager, JavaOnionProxyContext> {
|
||||
|
||||
public JavaTorNode(File torDirectory) throws IOException {
|
||||
super(new JavaOnionProxyManager(new JavaOnionProxyContext(torDirectory)));
|
||||
}
|
||||
|
||||
}
|
@ -9,6 +9,7 @@ import io.nucleo.net.proto.exceptions.ProtocolViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
@ -28,362 +29,363 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class Node {
|
||||
|
||||
/**
|
||||
* Use this whenever to flush the socket header over the socket!
|
||||
*
|
||||
* @param socket the socket to construct an objectOutputStream from
|
||||
* @return the outputstream from the socket
|
||||
* @throws IOException in case something goes wrong, duh!
|
||||
*/
|
||||
static ObjectOutputStream prepareOOSForSocket(Socket socket) throws IOException {
|
||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
||||
/**
|
||||
* Use this whenever to flush the socket header over the socket!
|
||||
*
|
||||
* @param socket the socket to construct an objectOutputStream from
|
||||
* @return the outputstream from the socket
|
||||
* @throws IOException in case something goes wrong, duh!
|
||||
*/
|
||||
static ObjectOutputStream prepareOOSForSocket(Socket socket) throws IOException {
|
||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
||||
|
||||
out.flush();
|
||||
return out;
|
||||
out.flush();
|
||||
return out;
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Node.class);
|
||||
|
||||
private final ServiceDescriptor descriptor;
|
||||
|
||||
private final HashMap<String, Connection> connections;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final TorNode tor;
|
||||
|
||||
private final AtomicBoolean serverRunning;
|
||||
|
||||
public Node(TCPServiceDescriptor descriptor) {
|
||||
this(null, descriptor);
|
||||
}
|
||||
|
||||
public Node(HiddenServiceDescriptor descriptor, TorNode<?, ?> tor) {
|
||||
this(tor, descriptor);
|
||||
}
|
||||
|
||||
private Node(TorNode<?, ?> tor, ServiceDescriptor descriptor) {
|
||||
this.connections = new HashMap<>();
|
||||
this.descriptor = descriptor;
|
||||
this.tor = tor;
|
||||
this.serverRunning = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public String getLocalName() {
|
||||
return descriptor.getFullAddress();
|
||||
}
|
||||
|
||||
public Connection connect(String peer, Collection<ConnectionListener> listeners)
|
||||
throws NumberFormatException, IOException {
|
||||
if (!serverRunning.get()) {
|
||||
throw new IOException("This node has not been started yet!");
|
||||
}
|
||||
if (peer.equals(descriptor.getFullAddress()))
|
||||
throw new IOException("If you find yourself talking to yourself too often, you should really seek help!");
|
||||
synchronized (connections) {
|
||||
if (connections.containsKey(peer))
|
||||
throw new IOException("Already connected to " + peer);
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Node.class);
|
||||
final Socket sock = connectToService(peer);
|
||||
return new OutgoingConnection(peer, sock, listeners);
|
||||
}
|
||||
|
||||
private final ServiceDescriptor descriptor;
|
||||
private Socket connectToService(String hostname, int port) throws IOException, UnknownHostException, SocketException {
|
||||
final Socket sock;
|
||||
if (tor != null)
|
||||
sock = tor.connectToHiddenService(hostname, port);
|
||||
else
|
||||
sock = new Socket(hostname, port);
|
||||
sock.setSoTimeout(60000);
|
||||
return sock;
|
||||
}
|
||||
|
||||
private final HashMap<String, Connection> connections;
|
||||
private Socket connectToService(String peer) throws IOException, UnknownHostException, SocketException {
|
||||
final String[] split = peer.split(Pattern.quote(":"));
|
||||
return connectToService(split[0], Integer.parseInt(split[1]));
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final TorNode tor;
|
||||
}
|
||||
|
||||
private final AtomicBoolean serverRunning;
|
||||
public synchronized Server startListening(ServerConnectListener listener) throws IOException {
|
||||
if (serverRunning.getAndSet(true))
|
||||
throw new IOException("This node is already listening!");
|
||||
final Server server = new Server(descriptor.getServerSocket(), listener);
|
||||
server.start();
|
||||
return server;
|
||||
}
|
||||
|
||||
public Node(TCPServiceDescriptor descriptor) {
|
||||
this(null, descriptor);
|
||||
public Connection getConnection(String peerAddress) {
|
||||
synchronized (connections) {
|
||||
return connections.get(peerAddress);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Connection> getConnections() {
|
||||
synchronized (connections) {
|
||||
return new HashSet<Connection>(connections.values());
|
||||
}
|
||||
}
|
||||
|
||||
public class Server extends Thread {
|
||||
|
||||
private boolean running;
|
||||
|
||||
private final ServerSocket serverSocket;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
private final ServerConnectListener serverConnectListener;
|
||||
|
||||
private Server(ServerSocket serverSocket, ServerConnectListener listener) {
|
||||
super("Server");
|
||||
this.serverSocket = descriptor.getServerSocket();
|
||||
this.serverConnectListener = listener;
|
||||
running = true;
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
public Node(HiddenServiceDescriptor descriptor, TorNode<?, ?> tor) {
|
||||
this(tor, descriptor);
|
||||
}
|
||||
|
||||
private Node(TorNode<?, ?> tor, ServiceDescriptor descriptor) {
|
||||
this.connections = new HashMap<>();
|
||||
this.descriptor = descriptor;
|
||||
this.tor = tor;
|
||||
this.serverRunning = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public String getLocalName() {
|
||||
return descriptor.getFullAddress();
|
||||
}
|
||||
|
||||
public Connection connect(String peer, Collection<ConnectionListener> listeners)
|
||||
throws NumberFormatException, IOException {
|
||||
if (!serverRunning.get()) {
|
||||
throw new IOException("This node has not been started yet!");
|
||||
public void shutdown() throws IOException {
|
||||
running = false;
|
||||
synchronized (connections) {
|
||||
final Set<Connection> conns = new HashSet<Connection>(connections.values());
|
||||
for (Connection con : conns) {
|
||||
con.close();
|
||||
}
|
||||
if (peer.equals(descriptor.getFullAddress()))
|
||||
throw new IOException("If you find yourself talking to yourself too often, you should really seek help!");
|
||||
synchronized (connections) {
|
||||
if (connections.containsKey(peer))
|
||||
throw new IOException("Already connected to " + peer);
|
||||
}
|
||||
serverSocket.close();
|
||||
try {
|
||||
executorService.awaitTermination(2, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Node.this.serverRunning.set(false);
|
||||
log.debug("Server successfully shutdown");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (running) {
|
||||
final Socket socket = serverSocket.accept();
|
||||
log.info("Accepting Client on port " + socket.getLocalPort());
|
||||
executorService.submit(new Acceptor(socket));
|
||||
}
|
||||
|
||||
final Socket sock = connectToService(peer);
|
||||
return new OutgoingConnection(peer, sock, listeners);
|
||||
} catch (IOException e) {
|
||||
if (running)
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private Socket connectToService(String hostname, int port) throws IOException, UnknownHostException, SocketException {
|
||||
final Socket sock;
|
||||
if (tor != null)
|
||||
sock = tor.connectToHiddenService(hostname, port);
|
||||
else
|
||||
sock = new Socket(hostname, port);
|
||||
sock.setSoTimeout(60000);
|
||||
return sock;
|
||||
private boolean verifyIdentity(HELOMessage helo, ObjectInputStream in) throws IOException {
|
||||
log.debug("Verifying HELO msg");
|
||||
final Socket sock = connectToService(helo.getHostname(), helo.getPort());
|
||||
|
||||
log.debug("Connected to advertised client " + helo.getPeer());
|
||||
ObjectOutputStream out = prepareOOSForSocket(sock);
|
||||
final IDMessage challenge = new IDMessage(descriptor);
|
||||
out.writeObject(challenge);
|
||||
log.debug("Sent IDMessage to");
|
||||
out.flush();
|
||||
// wait for other side to close
|
||||
try {
|
||||
while (sock.getInputStream().read() != -1)
|
||||
;
|
||||
} catch (IOException e) {
|
||||
// no matter
|
||||
}
|
||||
out.close();
|
||||
sock.close();
|
||||
log.debug("Closed socket after sending IDMessage");
|
||||
try {
|
||||
log.debug("Waiting for response of challenge");
|
||||
IDMessage response = (IDMessage) in.readObject();
|
||||
log.debug("Got response for challenge");
|
||||
final boolean verified = challenge.verify(response);
|
||||
log.debug("Response verified correctly!");
|
||||
return verified;
|
||||
} catch (ClassNotFoundException e) {
|
||||
new ProtocolViolationException(e).printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Socket connectToService(String peer) throws IOException, UnknownHostException, SocketException {
|
||||
final String[] split = peer.split(Pattern.quote(":"));
|
||||
return connectToService(split[0], Integer.parseInt(split[1]));
|
||||
private class Acceptor implements Runnable {
|
||||
|
||||
}
|
||||
private final Socket socket;
|
||||
|
||||
public synchronized Server startListening(ServerConnectListener listener) throws IOException {
|
||||
if (serverRunning.getAndSet(true))
|
||||
throw new IOException("This node is already listening!");
|
||||
final Server server = new Server(descriptor.getServerSocket(), listener);
|
||||
server.start();
|
||||
return server;
|
||||
}
|
||||
private Acceptor(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public Connection getConnection(String peerAddress) {
|
||||
synchronized (connections) {
|
||||
return connections.get(peerAddress);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Connection> getConnections() {
|
||||
synchronized (connections) {
|
||||
return new HashSet<Connection>(connections.values());
|
||||
}
|
||||
}
|
||||
|
||||
public class Server extends Thread {
|
||||
|
||||
private boolean running;
|
||||
|
||||
private final ServerSocket serverSocket;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
private final ServerConnectListener serverConnectListener;
|
||||
|
||||
private Server(ServerSocket serverSocket, ServerConnectListener listener) {
|
||||
super("Server");
|
||||
this.serverSocket = descriptor.getServerSocket();
|
||||
this.serverConnectListener = listener;
|
||||
running = true;
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
public void shutdown() throws IOException {
|
||||
running = false;
|
||||
synchronized (connections) {
|
||||
final Set<Connection> conns = new HashSet<Connection>(connections.values());
|
||||
for (Connection con : conns) {
|
||||
con.close();
|
||||
}
|
||||
}
|
||||
serverSocket.close();
|
||||
@Override
|
||||
public void run() {
|
||||
{
|
||||
try {
|
||||
socket.setSoTimeout(60 * 1000);
|
||||
} catch (SocketException e2) {
|
||||
e2.printStackTrace();
|
||||
try {
|
||||
executorService.awaitTermination(2, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Node.this.serverRunning.set(false);
|
||||
log.debug("Server successfully shutdown");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (running) {
|
||||
final Socket socket = serverSocket.accept();
|
||||
log.info("Accepting Client on port " + socket.getLocalPort());
|
||||
executorService.submit(new Acceptor(socket));
|
||||
}
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
if (running)
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private boolean verifyIdentity(HELOMessage helo, ObjectInputStream in) throws IOException {
|
||||
log.debug("Verifying HELO msg");
|
||||
final Socket sock = connectToService(helo.getHostname(), helo.getPort());
|
||||
ObjectInputStream objectInputStream = null;
|
||||
ObjectOutputStream out = null;
|
||||
|
||||
log.debug("Connected to advertised client " + helo.getPeer());
|
||||
ObjectOutputStream out = prepareOOSForSocket(sock);
|
||||
final IDMessage challenge = new IDMessage(descriptor);
|
||||
out.writeObject(challenge);
|
||||
log.debug("Sent IDMessage to");
|
||||
out.flush();
|
||||
// wait for other side to close
|
||||
// get incoming data
|
||||
try {
|
||||
out = prepareOOSForSocket(socket);
|
||||
objectInputStream = new ObjectInputStream(socket.getInputStream());
|
||||
} catch (EOFException e) {
|
||||
log.info("Got bogus incoming connection");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
while (sock.getInputStream().read() != -1)
|
||||
;
|
||||
} catch (IOException e) {
|
||||
// no matter
|
||||
socket.close();
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
out.close();
|
||||
sock.close();
|
||||
log.debug("Closed socket after sending IDMessage");
|
||||
try {
|
||||
log.debug("Waiting for response of challenge");
|
||||
IDMessage response = (IDMessage) in.readObject();
|
||||
log.debug("Got response for challenge");
|
||||
final boolean verified = challenge.verify(response);
|
||||
log.debug("Response verified correctly!");
|
||||
return verified;
|
||||
} catch (ClassNotFoundException e) {
|
||||
new ProtocolViolationException(e).printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private class Acceptor implements Runnable {
|
||||
String peer = null;
|
||||
try {
|
||||
log.debug("Waiting for HELO or Identification");
|
||||
final Message helo = (Message) objectInputStream.readObject();
|
||||
if (helo instanceof HELOMessage) {
|
||||
peer = ((HELOMessage) helo).getPeer();
|
||||
log.debug("Got HELO from " + peer);
|
||||
boolean alreadyConnected;
|
||||
synchronized (connections) {
|
||||
alreadyConnected = connections.containsKey(peer);
|
||||
}
|
||||
if (alreadyConnected || !verifyIdentity((HELOMessage) helo, objectInputStream)) {
|
||||
log.debug(alreadyConnected ? ("already connected to " + peer) : "verification failed");
|
||||
out.writeObject(alreadyConnected ? ControlMessage.ALREADY_CONNECTED : ControlMessage.HANDSHAKE_FAILED);
|
||||
out.writeObject(ControlMessage.DISCONNECT);
|
||||
out.flush();
|
||||
out.close();
|
||||
objectInputStream.close();
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
log.debug("Verification of " + peer + " successful");
|
||||
} else if (helo instanceof IDMessage) {
|
||||
peer = ((IDMessage) helo).getPeer();
|
||||
log.debug("got IDMessage from " + peer);
|
||||
final Connection client = connections.get(peer);
|
||||
if (client != null) {
|
||||
log.debug("Got preexisting connection for " + peer);
|
||||
client.sendMsg(((IDMessage) helo).reply());
|
||||
log.debug("Sent response for challenge");
|
||||
} else {
|
||||
log.debug("Got IDMessage for unknown connection to " + peer);
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
objectInputStream.close();
|
||||
socket.close();
|
||||
log.debug("Closed socket for identification");
|
||||
return;
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
private Acceptor(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
{
|
||||
try {
|
||||
socket.setSoTimeout(60 * 1000);
|
||||
} catch (SocketException e2) {
|
||||
|
||||
e2.printStackTrace();
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectInputStream objectInputStream = null;
|
||||
ObjectOutputStream out = null;
|
||||
|
||||
// get incoming data
|
||||
try {
|
||||
out = prepareOOSForSocket(socket);
|
||||
objectInputStream = new ObjectInputStream(socket.getInputStream());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String peer = null;
|
||||
try {
|
||||
log.debug("Waiting for HELO or Identification");
|
||||
final Message helo = (Message) objectInputStream.readObject();
|
||||
if (helo instanceof HELOMessage) {
|
||||
peer = ((HELOMessage) helo).getPeer();
|
||||
log.debug("Got HELO from " + peer);
|
||||
boolean alreadyConnected;
|
||||
synchronized (connections) {
|
||||
alreadyConnected = connections.containsKey(peer);
|
||||
}
|
||||
if (alreadyConnected || !verifyIdentity((HELOMessage) helo, objectInputStream)) {
|
||||
log.debug(alreadyConnected ? ("already connected to " + peer) : "verification failed");
|
||||
out.writeObject(alreadyConnected ? ControlMessage.ALREADY_CONNECTED : ControlMessage.HANDSHAKE_FAILED);
|
||||
out.writeObject(ControlMessage.DISCONNECT);
|
||||
out.flush();
|
||||
out.close();
|
||||
objectInputStream.close();
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
log.debug("Verification of " + peer + " successful");
|
||||
} else if (helo instanceof IDMessage) {
|
||||
peer = ((IDMessage) helo).getPeer();
|
||||
log.debug("got IDMessage from " + peer);
|
||||
final Connection client = connections.get(peer);
|
||||
if (client != null) {
|
||||
log.debug("Got preexisting connection for " + peer);
|
||||
client.sendMsg(((IDMessage) helo).reply());
|
||||
log.debug("Sent response for challenge");
|
||||
} else {
|
||||
log.debug("Got IDMessage for unknown connection to " + peer);
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
objectInputStream.close();
|
||||
socket.close();
|
||||
log.debug("Closed socket for identification");
|
||||
return;
|
||||
|
||||
} else
|
||||
throw new ClassNotFoundException("First Message was neither HELO, nor ID");
|
||||
} catch (ClassNotFoundException e) {
|
||||
new ProtocolViolationException(e);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
objectInputStream.close();
|
||||
out.close();
|
||||
socket.close();
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Here we go
|
||||
log.debug("Incoming Connection ready!");
|
||||
try {
|
||||
// TODO: listeners are only added afterwards, so messages can be lost!
|
||||
IncomingConnection incomingConnection = new IncomingConnection(peer, socket, out, objectInputStream);
|
||||
serverConnectListener.onConnect(incomingConnection);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class IncomingConnection extends Connection {
|
||||
private IncomingConnection(String peer, Socket socket, ObjectOutputStream out, ObjectInputStream in)
|
||||
throws IOException {
|
||||
super(peer, socket, out, in);
|
||||
synchronized (connections) {
|
||||
connections.put(peer, this);
|
||||
}
|
||||
sendMsg(ControlMessage.AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen() throws ConnectionException {
|
||||
super.listen();
|
||||
onReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMessage(Message msg) throws IOException {
|
||||
if ((msg instanceof ControlMessage) && (ControlMessage.HEARTBEAT == msg)) {
|
||||
log.debug("RX+REPLY HEARTBEAT");
|
||||
try {
|
||||
sendMsg(ControlMessage.HEARTBEAT);
|
||||
} catch (IOException e) {
|
||||
onError(e);
|
||||
}
|
||||
} else
|
||||
super.onMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
synchronized (connections) {
|
||||
connections.remove(getPeer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncoming() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class OutgoingConnection extends Connection {
|
||||
|
||||
private OutgoingConnection(String peer, Socket socket, Collection<ConnectionListener> listeners)
|
||||
throws IOException {
|
||||
super(peer, socket);
|
||||
synchronized (connections) {
|
||||
connections.put(peer, this);
|
||||
}
|
||||
setConnectionListeners(listeners);
|
||||
throw new ClassNotFoundException("First Message was neither HELO, nor ID");
|
||||
} catch (ClassNotFoundException e) {
|
||||
new ProtocolViolationException(e);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
listen();
|
||||
} catch (ConnectionException e) {
|
||||
// Never happens
|
||||
objectInputStream.close();
|
||||
out.close();
|
||||
socket.close();
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
log.debug("Sending HELO");
|
||||
sendMsg(new HELOMessage(descriptor));
|
||||
log.debug("Sent HELO");
|
||||
return;
|
||||
}
|
||||
// Here we go
|
||||
log.debug("Incoming Connection ready!");
|
||||
try {
|
||||
// TODO: listeners are only added afterwards, so messages can be lost!
|
||||
IncomingConnection incomingConnection = new IncomingConnection(peer, socket, out, objectInputStream);
|
||||
serverConnectListener.onConnect(incomingConnection);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
synchronized (connections) {
|
||||
connections.remove(getPeer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncoming() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class IncomingConnection extends Connection {
|
||||
private IncomingConnection(String peer, Socket socket, ObjectOutputStream out, ObjectInputStream in)
|
||||
throws IOException {
|
||||
super(peer, socket, out, in);
|
||||
synchronized (connections) {
|
||||
connections.put(peer, this);
|
||||
}
|
||||
sendMsg(ControlMessage.AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen() throws ConnectionException {
|
||||
super.listen();
|
||||
onReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMessage(Message msg) throws IOException {
|
||||
if ((msg instanceof ControlMessage) && (ControlMessage.HEARTBEAT == msg)) {
|
||||
log.debug("RX+REPLY HEARTBEAT");
|
||||
try {
|
||||
sendMsg(ControlMessage.HEARTBEAT);
|
||||
} catch (IOException e) {
|
||||
onError(e);
|
||||
}
|
||||
} else
|
||||
super.onMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
synchronized (connections) {
|
||||
connections.remove(getPeer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncoming() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class OutgoingConnection extends Connection {
|
||||
|
||||
private OutgoingConnection(String peer, Socket socket, Collection<ConnectionListener> listeners)
|
||||
throws IOException {
|
||||
super(peer, socket);
|
||||
synchronized (connections) {
|
||||
connections.put(peer, this);
|
||||
}
|
||||
setConnectionListeners(listeners);
|
||||
try {
|
||||
listen();
|
||||
} catch (ConnectionException e) {
|
||||
// Never happens
|
||||
}
|
||||
log.debug("Sending HELO");
|
||||
sendMsg(new HELOMessage(descriptor));
|
||||
log.debug("Sent HELO");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
synchronized (connections) {
|
||||
connections.remove(getPeer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncoming() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +1,52 @@
|
||||
package io.nucleo.net;
|
||||
|
||||
public enum PredefinedDisconnectReason implements DisconnectReason {
|
||||
TIMEOUT("due to timed out", false, true),
|
||||
CONNECTION_CLOSED("as ordered", true),
|
||||
RESET("due to remote reset (EOF)", false, true),
|
||||
UNKNOWN("for unknown reasons", false);
|
||||
TIMEOUT("due to timed out", false, true),
|
||||
CONNECTION_CLOSED("as ordered", true),
|
||||
RESET("due to remote reset (EOF)", false, true),
|
||||
UNKNOWN("for unknown reasons", false);
|
||||
|
||||
private Boolean remote;
|
||||
private final boolean graceful;
|
||||
private final String description;
|
||||
private Boolean remote;
|
||||
private final boolean graceful;
|
||||
private final String description;
|
||||
|
||||
private PredefinedDisconnectReason(String description, boolean graceful) {
|
||||
this.description = description;
|
||||
this.graceful = graceful;
|
||||
}
|
||||
private PredefinedDisconnectReason(String description, boolean graceful) {
|
||||
this.description = description;
|
||||
this.graceful = graceful;
|
||||
}
|
||||
|
||||
private PredefinedDisconnectReason(String description, boolean graceful, boolean remote) {
|
||||
this.description = description;
|
||||
this.graceful = graceful;
|
||||
this.remote = remote;
|
||||
}
|
||||
private PredefinedDisconnectReason(String description, boolean graceful, boolean remote) {
|
||||
this.description = description;
|
||||
this.graceful = graceful;
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
public static PredefinedDisconnectReason createReason(PredefinedDisconnectReason reason, boolean remote) {
|
||||
reason.remote = remote;
|
||||
return reason;
|
||||
}
|
||||
public static PredefinedDisconnectReason createReason(PredefinedDisconnectReason reason, boolean remote) {
|
||||
reason.remote = remote;
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGraceful() {
|
||||
return graceful;
|
||||
}
|
||||
@Override
|
||||
public boolean isGraceful() {
|
||||
return graceful;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemote() {
|
||||
if (remote == null)
|
||||
return false;
|
||||
return remote;
|
||||
@Override
|
||||
public boolean isRemote() {
|
||||
if (remote == null)
|
||||
return false;
|
||||
return remote;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder bld = new StringBuilder("Connection closed ");
|
||||
if (remote != null)
|
||||
bld.append(remote ? "remotely " : "locally ");
|
||||
bld.append(description).append(" (");
|
||||
bld.append(graceful ? "graceful" : "irregular").append(" disconnect)");
|
||||
return bld.toString();
|
||||
public String toString() {
|
||||
StringBuilder bld = new StringBuilder("Connection closed ");
|
||||
if (remote != null)
|
||||
bld.append(remote ? "remotely " : "locally ");
|
||||
bld.append(description).append(" (");
|
||||
bld.append(graceful ? "graceful" : "irregular").append(" disconnect)");
|
||||
return bld.toString();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,11 +2,11 @@ package io.nucleo.net;
|
||||
|
||||
public interface ServerConnectListener {
|
||||
|
||||
/**
|
||||
* Called whenever an incoming connection was set up properly.
|
||||
* Connection.listen() needs to be called ASAP for the connection to become available
|
||||
*
|
||||
* @param con the newly established connection
|
||||
*/
|
||||
public void onConnect(Connection con);
|
||||
/**
|
||||
* Called whenever an incoming connection was set up properly.
|
||||
* Connection.listen() needs to be called ASAP for the connection to become available
|
||||
*
|
||||
* @param con the newly established connection
|
||||
*/
|
||||
public void onConnect(Connection con);
|
||||
}
|
||||
|
@ -7,39 +7,28 @@ import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public abstract class TorNode<M extends OnionProxyManager, C extends OnionProxyContext> {
|
||||
|
||||
private static final String PROXY_LOCALHOST = "127.0.0.1";
|
||||
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 int TRIES_PER_HS_STARTUP = 150;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TorNode.class);
|
||||
|
||||
private final OnionProxyManager tor;
|
||||
private final Socks5Proxy proxy;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TorNode(File torDirectory) throws IOException, InstantiationException {
|
||||
Class<M> mgr;
|
||||
Class<C> ctx;
|
||||
try {
|
||||
mgr = (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
ctx = (Class<C>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
|
||||
} catch (Throwable t) {
|
||||
throw new InstantiationException(
|
||||
"Could not reify Types of OnionProxyManager and OnionProxyContext! Is this class being used with raw types?");
|
||||
}
|
||||
log.debug("Running Tornode with " + mgr.getSimpleName() + " and " + ctx.getSimpleName());
|
||||
tor = initTor(torDirectory, mgr, ctx);
|
||||
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.info("TorSocks running on port " + proxyPort);
|
||||
this.proxy = setupSocksProxy(proxyPort);
|
||||
@ -85,78 +74,49 @@ public abstract class TorNode<M extends OnionProxyManager, C extends OnionProxyC
|
||||
throw new IOException("Cannot connect to hidden service");
|
||||
}
|
||||
|
||||
public HiddenServiceDescriptor createHiddenService(int localPort, int servicePort) throws IOException {
|
||||
long before = GregorianCalendar.getInstance().getTimeInMillis();
|
||||
String hiddenServiceName = tor.publishHiddenService(servicePort, localPort);
|
||||
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.info("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);
|
||||
return tryConnectToHiddenService(servicePort, before, hiddenServiceName, hiddenServiceDescriptor);
|
||||
|
||||
if (listener != null)
|
||||
tor.attachHiddenServiceReadyListener(hiddenServiceDescriptor, listener);
|
||||
return hiddenServiceDescriptor;
|
||||
}
|
||||
|
||||
private HiddenServiceDescriptor tryConnectToHiddenService(int servicePort, long before, String hiddenServiceName,
|
||||
final HiddenServiceDescriptor hiddenServiceDescriptor) throws IOException {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
hiddenServiceDescriptor.getServerSocket().accept().close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
for (int i = 0; i < TRIES_PER_HS_STARTUP; ++i) {
|
||||
try {
|
||||
final Socket socket = connectToHiddenService(hiddenServiceName, servicePort, 1, false);
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
log.info("Hidden service " + hiddenServiceName + ":" + servicePort + " is not yet reachable");
|
||||
try {
|
||||
Thread.sleep(RETRY_SLEEP);
|
||||
} catch (InterruptedException e1) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
log.info("Took " + (GregorianCalendar.getInstance().getTimeInMillis() - before)
|
||||
+ " milliseconds to connect to publish " + hiddenServiceName + ":" + servicePort);
|
||||
return hiddenServiceDescriptor;
|
||||
}
|
||||
throw new IOException("Could not publish Hidden Service!");
|
||||
}
|
||||
|
||||
public HiddenServiceDescriptor createHiddenService(int port) throws IOException {
|
||||
return createHiddenService(port, port);
|
||||
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(File torDir,
|
||||
Class<M> mgrType, Class<C> ctxType) throws IOException {
|
||||
static <M extends OnionProxyManager, C extends OnionProxyContext> OnionProxyManager initTor(final M mgr, C ctx)
|
||||
throws IOException {
|
||||
|
||||
log.debug("Trying to start tor in directory {}", torDir);
|
||||
C ctx;
|
||||
final M onionProxyManager;
|
||||
try {
|
||||
ctx = ctxType.getConstructor(File.class).newInstance(torDir);
|
||||
onionProxyManager = mgrType.getConstructor(OnionProxyContext.class).newInstance(ctx);
|
||||
} catch (Exception e1) {
|
||||
throw new IOException(e1);
|
||||
}
|
||||
log.debug("Trying to start tor in directory {}", mgr.getWorkingDirectory());
|
||||
|
||||
try {
|
||||
if (!onionProxyManager.startWithRepeat(TOTAL_SEC_PER_STARTUP, TRIES_PER_STARTUP)) {
|
||||
if (!mgr.startWithRepeat(TOTAL_SEC_PER_STARTUP, TRIES_PER_STARTUP)) {
|
||||
throw new IOException("Could not Start Tor. Is another instance already running?");
|
||||
} else {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
onionProxyManager.stop();
|
||||
mgr.stop();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@ -165,6 +125,6 @@ public abstract class TorNode<M extends OnionProxyManager, C extends OnionProxyC
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
return onionProxyManager;
|
||||
return mgr;
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ import java.io.Serializable;
|
||||
public class ContainerMessage implements Message {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 9219884444024922023L;
|
||||
private final Serializable payload;
|
||||
private static final long serialVersionUID = 9219884444024922023L;
|
||||
private final Serializable payload;
|
||||
|
||||
public ContainerMessage(Serializable payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
public ContainerMessage(Serializable payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public Serializable getPayload() {
|
||||
return payload;
|
||||
}
|
||||
public Serializable getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package io.nucleo.net.proto;
|
||||
|
||||
public enum ControlMessage implements Message {
|
||||
HEARTBEAT,
|
||||
AVAILABLE,
|
||||
HANDSHAKE_FAILED,
|
||||
ALREADY_CONNECTED,
|
||||
DISCONNECT;
|
||||
HEARTBEAT,
|
||||
AVAILABLE,
|
||||
HANDSHAKE_FAILED,
|
||||
ALREADY_CONNECTED,
|
||||
DISCONNECT;
|
||||
}
|
||||
|
@ -6,30 +6,30 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class HELOMessage implements Message {
|
||||
|
||||
private static final long serialVersionUID = -4582946298578924930L;
|
||||
private final String peer;
|
||||
private static final long serialVersionUID = -4582946298578924930L;
|
||||
private final String peer;
|
||||
|
||||
public HELOMessage(ServiceDescriptor descriptor) {
|
||||
this(descriptor.getFullAddress());
|
||||
}
|
||||
public HELOMessage(ServiceDescriptor descriptor) {
|
||||
this(descriptor.getFullAddress());
|
||||
}
|
||||
|
||||
private HELOMessage(String peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
private HELOMessage(String peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
public String getPeer() {
|
||||
return peer;
|
||||
}
|
||||
public String getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return peer.split(Pattern.quote(":"))[0];
|
||||
}
|
||||
public String getHostname() {
|
||||
return peer.split(Pattern.quote(":"))[0];
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return Integer.parseInt(peer.split(Pattern.quote(":"))[1]);
|
||||
}
|
||||
public int getPort() {
|
||||
return Integer.parseInt(peer.split(Pattern.quote(":"))[1]);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "HELO " + peer;
|
||||
}
|
||||
public String toString() {
|
||||
return "HELO " + peer;
|
||||
}
|
||||
}
|
||||
|
@ -7,42 +7,42 @@ import java.security.SecureRandom;
|
||||
|
||||
public class IDMessage implements Message {
|
||||
|
||||
private static final long serialVersionUID = -2214485311644580948L;
|
||||
private static SecureRandom rnd;
|
||||
private static final long serialVersionUID = -2214485311644580948L;
|
||||
private static SecureRandom rnd;
|
||||
|
||||
static {
|
||||
try {
|
||||
rnd = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
static {
|
||||
try {
|
||||
rnd = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private final String id;
|
||||
private final long nonce;
|
||||
private final String id;
|
||||
private final long nonce;
|
||||
|
||||
public IDMessage(ServiceDescriptor descriptor) {
|
||||
this(descriptor.getFullAddress(), rnd.nextLong());
|
||||
}
|
||||
public IDMessage(ServiceDescriptor descriptor) {
|
||||
this(descriptor.getFullAddress(), rnd.nextLong());
|
||||
}
|
||||
|
||||
private IDMessage(String id, long nonce) {
|
||||
this.id = id;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
private IDMessage(String id, long nonce) {
|
||||
this.id = id;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
public String getPeer() {
|
||||
return id;
|
||||
}
|
||||
public String getPeer() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public IDMessage reply() {
|
||||
return new IDMessage(id, nonce);
|
||||
}
|
||||
public IDMessage reply() {
|
||||
return new IDMessage(id, nonce);
|
||||
}
|
||||
|
||||
public boolean verify(IDMessage msg) {
|
||||
return id.equals(msg.id) && (nonce == msg.nonce);
|
||||
}
|
||||
public boolean verify(IDMessage msg) {
|
||||
return id.equals(msg.id) && (nonce == msg.nonce);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ID " + id;
|
||||
}
|
||||
public String toString() {
|
||||
return "ID " + id;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
package io.nucleo.net.proto.exceptions;
|
||||
|
||||
public class ConnectionException extends Exception {
|
||||
public ConnectionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
public ConnectionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ConnectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
public ConnectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ConnectionException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
public ConnectionException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,18 +2,18 @@ package io.nucleo.net.proto.exceptions;
|
||||
|
||||
public class ProtocolViolationException extends Exception {
|
||||
|
||||
public ProtocolViolationException() {
|
||||
}
|
||||
public ProtocolViolationException() {
|
||||
}
|
||||
|
||||
public ProtocolViolationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
public ProtocolViolationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ProtocolViolationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
public ProtocolViolationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ProtocolViolationException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
public ProtocolViolationException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
ControlPort auto
|
||||
CookieAuthentication 1
|
||||
DisableNetwork 1
|
||||
AvoidDiskWrites 1
|
||||
PidFile pid
|
||||
RunAsDaemon 1
|
||||
SafeSocks 1
|
||||
|
@ -13,18 +13,27 @@ import java.util.concurrent.ExecutionException;
|
||||
public class TorNodeTest {
|
||||
|
||||
private static final int hsPort = 55555;
|
||||
private static CountDownLatch serverLatch = new CountDownLatch(1);
|
||||
private static CountDownLatch serverLatch = new CountDownLatch(2);
|
||||
|
||||
private static TorNode<JavaOnionProxyManager, JavaOnionProxyContext> node;
|
||||
|
||||
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException, InstantiationException {
|
||||
public static void main(String[] args)
|
||||
throws IOException, InterruptedException, ExecutionException, InstantiationException {
|
||||
File dir = new File("tor-test");
|
||||
dir.mkdirs();
|
||||
for (String str : args)
|
||||
System.out.print(str + " ");
|
||||
node = new TorNode<JavaOnionProxyManager, JavaOnionProxyContext>(dir) {
|
||||
};
|
||||
final ServiceDescriptor hiddenService = node.createHiddenService(hsPort);
|
||||
node = new JavaTorNode(dir);
|
||||
final ServiceDescriptor hiddenService = node.createHiddenService(hsPort, new HiddenServiceReadyListener() {
|
||||
|
||||
@Override
|
||||
public void onConnect(HiddenServiceDescriptor descriptor) {
|
||||
|
||||
System.out.println("Successfully published hidden service " + descriptor.getFullAddress());
|
||||
serverLatch.countDown();
|
||||
|
||||
}
|
||||
});
|
||||
new Thread(new Server(hiddenService.getServerSocket())).start();
|
||||
serverLatch.await();
|
||||
|
||||
@ -94,7 +103,8 @@ public class TorNodeTest {
|
||||
while (true) {
|
||||
|
||||
Socket sock = socket.accept();
|
||||
System.out.println("Accepting Client " + sock.getRemoteSocketAddress() + " on port " + sock.getLocalPort());
|
||||
System.out.println(
|
||||
"Accepting Client " + sock.getRemoteSocketAddress() + " on port " + sock.getLocalPort());
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
|
||||
OutputStreamWriter out = new OutputStreamWriter(sock.getOutputStream());
|
||||
String aLine = null;
|
||||
|
@ -61,11 +61,16 @@ public class NodeTest {
|
||||
if (args.length == 2) {
|
||||
File dir = new File(args[0]);
|
||||
dir.mkdirs();
|
||||
TorNode<JavaOnionProxyManager, JavaOnionProxyContext> tor = new TorNode<JavaOnionProxyManager, JavaOnionProxyContext>(
|
||||
dir) {
|
||||
};
|
||||
TorNode<JavaOnionProxyManager, JavaOnionProxyContext> tor = new JavaTorNode(dir);
|
||||
|
||||
node = new Node(tor.createHiddenService(Integer.parseInt(args[1])), tor);
|
||||
node = new Node(tor.createHiddenService(Integer.parseInt(args[1]), new HiddenServiceReadyListener() {
|
||||
|
||||
@Override
|
||||
public void onConnect(HiddenServiceDescriptor descriptor) {
|
||||
System.out.println("Successfully published hidden service " + descriptor.getFullAddress() + " ");
|
||||
|
||||
}
|
||||
}), tor);
|
||||
} else {
|
||||
node = new Node(new TCPServiceDescriptor("localhost", Integer.parseInt(args[0])));
|
||||
}
|
||||
@ -138,7 +143,7 @@ public class NodeTest {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
System.out.print("\n" + node.getLocalName() + ":" + (currentCon == null ? "" : currentCon.getPeer()) + " >");
|
||||
System.out.print("\n" + node.getLocalName() + (currentCon == null ? "" : (":" + currentCon.getPeer())) + " >");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -118,9 +118,9 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
|
||||
private void init(int networkId, File storageDir) {
|
||||
Log.traceCall();
|
||||
|
||||
Address persisted = dbStorage.initAndGetPersisted("myOnionAddress");
|
||||
if (persisted != null)
|
||||
this.myOnionAddress = persisted;
|
||||
Address persistedOnionAddress = dbStorage.initAndGetPersisted("myOnionAddress");
|
||||
if (persistedOnionAddress != null)
|
||||
this.myOnionAddress = persistedOnionAddress;
|
||||
|
||||
// network
|
||||
networkNode = useLocalhost ? new LocalhostNetworkNode(port) : new TorNetworkNode(port, torDir);
|
||||
|
@ -12,6 +12,7 @@ import io.bitsquare.common.util.Utilities;
|
||||
import io.bitsquare.p2p.Address;
|
||||
import io.bitsquare.p2p.Utils;
|
||||
import io.nucleo.net.HiddenServiceDescriptor;
|
||||
import io.nucleo.net.JavaTorNode;
|
||||
import io.nucleo.net.TorNode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -84,8 +85,6 @@ public class TorNetworkNode extends NetworkNode {
|
||||
|
||||
startServer(hiddenServiceDescriptor.getServerSocket());
|
||||
setupListeners.stream().forEach(e -> e.onHiddenServicePublished());
|
||||
/* UserThread.runAfter(() -> setupListeners.stream().forEach(e -> e.onHiddenServicePublished()),
|
||||
500, TimeUnit.MILLISECONDS);*/
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -214,9 +213,7 @@ public class TorNetworkNode extends NetworkNode {
|
||||
|
||||
log.info("TorDir = " + torDir.getAbsolutePath());
|
||||
log.trace("Create TorNode");
|
||||
TorNode<JavaOnionProxyManager, JavaOnionProxyContext> torNode =
|
||||
new TorNode<JavaOnionProxyManager, JavaOnionProxyContext>(torDir) {
|
||||
};
|
||||
TorNode<JavaOnionProxyManager, JavaOnionProxyContext> torNode = new JavaTorNode(torDir);
|
||||
log.info("\n\n############################################################\n" +
|
||||
"TorNode created:" +
|
||||
"\nTook " + (System.currentTimeMillis() - ts) + " ms"
|
||||
@ -245,28 +242,31 @@ public class TorNetworkNode extends NetworkNode {
|
||||
private void createHiddenService(TorNode torNode, int localPort, int servicePort,
|
||||
Consumer<HiddenServiceDescriptor> resultHandler) {
|
||||
Log.traceCall();
|
||||
ListenableFuture<HiddenServiceDescriptor> future = executorService.submit(() -> {
|
||||
ListenableFuture<Object> future = executorService.submit(() -> {
|
||||
Utilities.setThreadName("TorNetworkNode:CreateHiddenService");
|
||||
try {
|
||||
long ts = System.currentTimeMillis();
|
||||
log.debug("Create hidden service");
|
||||
HiddenServiceDescriptor hiddenServiceDescriptor = torNode.createHiddenService(localPort, servicePort);
|
||||
log.info("\n\n############################################################\n" +
|
||||
"Hidden service created:" +
|
||||
"\nAddress=" + hiddenServiceDescriptor.getFullAddress() +
|
||||
"\nTook " + (System.currentTimeMillis() - ts) + " ms"
|
||||
+ "\n############################################################\n");
|
||||
|
||||
return hiddenServiceDescriptor;
|
||||
torNode.addHiddenServiceReadyListener(hiddenServiceDescriptor, descriptor -> {
|
||||
log.info("\n\n############################################################\n" +
|
||||
"Hidden service published:" +
|
||||
"\nAddress=" + descriptor.getFullAddress() +
|
||||
"\nTook " + (System.currentTimeMillis() - ts) + " ms"
|
||||
+ "\n############################################################\n");
|
||||
|
||||
UserThread.execute(() -> resultHandler.accept(hiddenServiceDescriptor));
|
||||
});
|
||||
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
throw t;
|
||||
}
|
||||
});
|
||||
Futures.addCallback(future, new FutureCallback<HiddenServiceDescriptor>() {
|
||||
public void onSuccess(HiddenServiceDescriptor hiddenServiceDescriptor) {
|
||||
UserThread.execute(() -> {
|
||||
resultHandler.accept(hiddenServiceDescriptor);
|
||||
});
|
||||
Futures.addCallback(future, new FutureCallback<Object>() {
|
||||
public void onSuccess(Object hiddenServiceDescriptor) {
|
||||
log.debug("HiddenServiceDescriptor created. Wait for publishing.");
|
||||
}
|
||||
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
|
@ -120,8 +120,8 @@ public class TestUtils {
|
||||
seedNodesRepository.setTorSeedNodeAddresses(seedNodes);
|
||||
}
|
||||
|
||||
P2PService p2PService = new P2PService(seedNodesRepository, port, new File("seed_node_" + port), useLocalhost, 2,
|
||||
encryptionService, keyRing, new File("dummy"));
|
||||
P2PService p2PService = new P2PService(seedNodesRepository, port, new File("seed_node_" + port), useLocalhost,
|
||||
2, new File("dummy"), encryptionService, keyRing);
|
||||
p2PService.start(new P2PServiceListener() {
|
||||
@Override
|
||||
public void onRequestingDataCompleted() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user