Update to master of jtorproxy

This commit is contained in:
Manfred Karrer 2015-11-17 23:54:57 +01:00
parent cefcf5b3d8
commit 9b1108aa0a
30 changed files with 1043 additions and 924 deletions

View File

@ -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");
}
}
}

View File

@ -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)

View File

@ -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();

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -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();
}
}
}
}
}
}
}
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -0,0 +1,7 @@
package io.nucleo.net;
public interface HiddenServiceReadyListener {
public void onConnect(HiddenServiceDescriptor descriptor);
}

View 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)));
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,6 +1,7 @@
ControlPort auto
CookieAuthentication 1
DisableNetwork 1
AvoidDiskWrites 1
PidFile pid
RunAsDaemon 1
SafeSocks 1

View File

@ -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;

View File

@ -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())) + " >");
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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() {