Skip to content
Snippets Groups Projects
Commit 2ee68f44 authored by Torsten Grote's avatar Torsten Grote
Browse files

Merge branch '3-4-10-wrapper-states' into 'master'

Add more wrapper states, allow wrapper to be reused

Closes #10, #4, and #3

See merge request !9
parents 88d61cbe db9ebfd6
No related branches found
No related tags found
1 merge request!9Add more wrapper states, allow wrapper to be reused
Pipeline #14637 passed
...@@ -5,7 +5,6 @@ import android.content.pm.PackageInfo; ...@@ -5,7 +5,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Build;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLock; import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLock;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager; import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
...@@ -24,6 +23,9 @@ import java.util.logging.Logger; ...@@ -24,6 +23,9 @@ import java.util.logging.Logger;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import static android.os.Build.CPU_ABI;
import static android.os.Build.CPU_ABI2;
import static android.os.Build.SUPPORTED_ABIS;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
...@@ -42,8 +44,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -42,8 +44,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
private static final String OBFS4_LIB_NAME = "libobfs4proxy.so"; private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so"; private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so";
private static final Logger LOG = private static final Logger LOG = getLogger(AndroidTorWrapper.class.getName());
getLogger(AndroidTorWrapper.class.getName());
private final Application app; private final Application app;
private final AndroidWakeLock wakeLock; private final AndroidWakeLock wakeLock;
...@@ -74,8 +75,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -74,8 +75,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
File torDirectory, File torDirectory,
int torSocksPort, int torSocksPort,
int torControlPort) { int torControlPort) {
super(ioExecutor, eventExecutor, architecture, torDirectory, super(ioExecutor, eventExecutor, architecture, torDirectory, torSocksPort, torControlPort);
torSocksPort, torControlPort);
this.app = app; this.app = app;
wakeLock = wakeLockManager.createWakeLock("TorPlugin"); wakeLock = wakeLockManager.createWakeLock("TorPlugin");
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir; String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
...@@ -119,7 +119,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -119,7 +119,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
} }
@Override @Override
public void stop() throws IOException { public void stop() throws IOException, InterruptedException {
try { try {
super.stop(); super.stop();
} finally { } finally {
...@@ -139,8 +139,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -139,8 +139,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
@Override @Override
protected File getSnowflakeExecutableFile() { protected File getSnowflakeExecutableFile() {
return snowflakeLib.exists() ? snowflakeLib return snowflakeLib.exists() ? snowflakeLib : super.getSnowflakeExecutableFile();
: super.getSnowflakeExecutableFile();
} }
@Override @Override
...@@ -150,18 +149,15 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -150,18 +149,15 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
@Override @Override
protected void installObfs4Executable() throws IOException { protected void installObfs4Executable() throws IOException {
installExecutable(super.getObfs4ExecutableFile(), obfs4Lib, installExecutable(super.getObfs4ExecutableFile(), obfs4Lib, OBFS4_LIB_NAME);
OBFS4_LIB_NAME);
} }
@Override @Override
protected void installSnowflakeExecutable() throws IOException { protected void installSnowflakeExecutable() throws IOException {
installExecutable(super.getSnowflakeExecutableFile(), snowflakeLib, installExecutable(super.getSnowflakeExecutableFile(), snowflakeLib, SNOWFLAKE_LIB_NAME);
SNOWFLAKE_LIB_NAME);
} }
private void installExecutable(File extracted, File lib, String libName) private void installExecutable(File extracted, File lib, String libName) throws IOException {
throws IOException {
if (lib.exists()) { if (lib.exists()) {
// If an older version left behind a binary, delete it // If an older version left behind a binary, delete it
if (extracted.exists()) { if (extracted.exists()) {
...@@ -177,8 +173,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -177,8 +173,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
} }
} }
private void extractLibraryFromApk(String libName, File dest) private void extractLibraryFromApk(String libName, File dest) throws IOException {
throws IOException {
File sourceDir = new File(app.getApplicationInfo().sourceDir); File sourceDir = new File(app.getApplicationInfo().sourceDir);
if (sourceDir.isFile()) { if (sourceDir.isFile()) {
// Look for other APK files in the same directory, if we're allowed // Look for other APK files in the same directory, if we're allowed
...@@ -189,12 +184,10 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -189,12 +184,10 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
for (File apk : findApkFiles(sourceDir)) { for (File apk : findApkFiles(sourceDir)) {
@SuppressWarnings("IOStreamConstructor") @SuppressWarnings("IOStreamConstructor")
ZipInputStream zin = new ZipInputStream(new FileInputStream(apk)); ZipInputStream zin = new ZipInputStream(new FileInputStream(apk));
for (ZipEntry e = zin.getNextEntry(); e != null; for (ZipEntry e = zin.getNextEntry(); e != null; e = zin.getNextEntry()) {
e = zin.getNextEntry()) {
if (libPaths.contains(e.getName())) { if (libPaths.contains(e.getName())) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Extracting " + e.getName() LOG.info("Extracting " + e.getName() + " from " + apk.getAbsolutePath());
+ " from " + apk.getAbsolutePath());
} }
extract(zin, dest); // Zip input stream will be closed extract(zin, dest); // Zip input stream will be closed
return; return;
...@@ -243,10 +236,10 @@ public class AndroidTorWrapper extends AbstractTorWrapper { ...@@ -243,10 +236,10 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
private Collection<String> getSupportedArchitectures() { private Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>(); List<String> abis = new ArrayList<>();
if (SDK_INT >= 21) { if (SDK_INT >= 21) {
abis.addAll(asList(Build.SUPPORTED_ABIS)); abis.addAll(asList(SUPPORTED_ABIS));
} else { } else {
abis.add(Build.CPU_ABI); abis.add(CPU_ABI);
if (Build.CPU_ABI2 != null) abis.add(Build.CPU_ABI2); if (CPU_ABI2 != null) abis.add(CPU_ABI2);
} }
return abis; return abis;
} }
......
...@@ -19,8 +19,9 @@ import java.util.ArrayList; ...@@ -19,8 +19,9 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
...@@ -44,7 +45,11 @@ import static org.briarproject.onionwrapper.TorUtils.tryToClose; ...@@ -44,7 +45,11 @@ import static org.briarproject.onionwrapper.TorUtils.tryToClose;
import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTED; import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTED;
import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTING; import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTING;
import static org.briarproject.onionwrapper.TorWrapper.TorState.DISABLED; import static org.briarproject.onionwrapper.TorWrapper.TorState.DISABLED;
import static org.briarproject.onionwrapper.TorWrapper.TorState.STARTING_STOPPING; import static org.briarproject.onionwrapper.TorWrapper.TorState.NOT_STARTED;
import static org.briarproject.onionwrapper.TorWrapper.TorState.STARTED;
import static org.briarproject.onionwrapper.TorWrapper.TorState.STARTING;
import static org.briarproject.onionwrapper.TorWrapper.TorState.STOPPED;
import static org.briarproject.onionwrapper.TorWrapper.TorState.STOPPING;
@InterfaceNotNullByDefault @InterfaceNotNullByDefault
abstract class AbstractTorWrapper implements EventHandler, TorWrapper { abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
...@@ -63,8 +68,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -63,8 +68,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private static final String OWNER = "__OwningControllerProcess"; private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000; private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern BOOTSTRAP_PERCENTAGE = private static final Pattern BOOTSTRAP_PERCENTAGE = Pattern.compile(".*PROGRESS=(\\d{1,3}).*");
Pattern.compile(".*PROGRESS=(\\d{1,3}).*");
protected final Executor ioExecutor; protected final Executor ioExecutor;
protected final Executor eventExecutor; protected final Executor eventExecutor;
...@@ -72,10 +76,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -72,10 +76,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private final File torDirectory, configFile, doneFile, cookieFile; private final File torDirectory, configFile, doneFile, cookieFile;
private final int torSocksPort; private final int torSocksPort;
private final int torControlPort; private final int torControlPort;
private final AtomicBoolean used = new AtomicBoolean(false);
protected final NetworkState state = new NetworkState(); protected final NetworkState state = new NetworkState();
private volatile Process torProcess = null;
private volatile Socket controlSocket = null; private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null; private volatile TorControlConnection controlConnection = null;
...@@ -83,8 +87,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -83,8 +87,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
protected abstract long getLastUpdateTime(); protected abstract long getLastUpdateTime();
protected abstract InputStream getResourceInputStream(String name, protected abstract InputStream getResourceInputStream(String name, String extension);
String extension);
AbstractTorWrapper(Executor ioExecutor, AbstractTorWrapper(Executor ioExecutor,
Executor eventExecutor, Executor eventExecutor,
...@@ -123,7 +126,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -123,7 +126,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
@Override @Override
public void start() throws IOException, InterruptedException { public void start() throws IOException, InterruptedException {
if (used.getAndSet(true)) throw new IllegalStateException(); state.setStarting();
if (!torDirectory.exists()) { if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) { if (!torDirectory.mkdirs()) {
throw new IOException("Could not create Tor directory"); throw new IOException("Could not create Tor directory");
...@@ -133,17 +136,16 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -133,17 +136,16 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
if (!assetsAreUpToDate()) installAssets(); if (!assetsAreUpToDate()) installAssets();
// Start from the default config every time // Start from the default config every time
extract(getConfigInputStream(), configFile); extract(getConfigInputStream(), configFile);
if (cookieFile.exists() && !cookieFile.delete()) if (cookieFile.exists() && !cookieFile.delete()) {
LOG.warning("Old auth cookie not deleted"); LOG.warning("Old auth cookie not deleted");
}
// Start a new Tor process // Start a new Tor process
LOG.info("Starting Tor"); LOG.info("Starting Tor");
File torFile = getTorExecutableFile(); File torFile = getTorExecutableFile();
String torPath = torFile.getAbsolutePath(); String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath(); String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(getProcessId()); String pid = String.valueOf(getProcessId());
Process torProcess; ProcessBuilder pb = new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
Map<String, String> env = pb.environment(); Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath()); env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory); pb.directory(torDirectory);
...@@ -154,7 +156,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -154,7 +156,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
throw new IOException(e); throw new IOException(e);
} }
// Wait for the Tor process to start // Wait for the Tor process to start
waitForTorToStart(torProcess); waitForTorToStart(requireNonNull(torProcess));
// Wait for the auth cookie file to be created/updated // Wait for the auth cookie file to be created/updated
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (cookieFile.length() < 32) { while (cookieFile.length() < 32) {
...@@ -243,8 +245,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -243,8 +245,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private InputStream getExecutableInputStream(String basename) { private InputStream getExecutableInputStream(String basename) {
String ext = getExecutableExtension(); String ext = getExecutableExtension();
return requireNonNull( return requireNonNull(getResourceInputStream(architecture + "/" + basename, ext));
getResourceInputStream(architecture + "/" + basename, ext));
} }
protected String getExecutableExtension() { protected String getExecutableExtension() {
...@@ -265,7 +266,6 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -265,7 +266,6 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
append(strb, "CookieAuthentication", 1); append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath()); append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1); append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1); append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort); append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n"); strb.append("GeoIPFile\n");
...@@ -295,29 +295,48 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -295,29 +295,48 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
} }
protected void waitForTorToStart(Process torProcess) protected void waitForTorToStart(Process torProcess) throws InterruptedException, IOException {
throws InterruptedException, IOException { // Wait for the control port to be opened, then continue to read Tor's
Scanner stdout = new Scanner(torProcess.getInputStream()); // stdout and stderr in a background thread until it exits.
// Log the first line of stdout (contains Tor and library versions) BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
if (stdout.hasNextLine()) LOG.info(stdout.nextLine()); ioExecutor.execute(() -> {
// Read the process's stdout (and redirected stderr) until it detaches boolean started = false;
while (stdout.hasNextLine()) stdout.nextLine(); // Read the process's stdout (and redirected stderr)
stdout.close(); Scanner stdout = new Scanner(torProcess.getInputStream());
// Wait for the process to detach or exit // Log the first line of stdout (contains Tor and library versions)
int exit = torProcess.waitFor(); if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
if (exit != 0) throw new IOException("Tor exited with value " + exit); // Startup has succeeded when the control port is open
while (stdout.hasNextLine()) {
String line = stdout.nextLine();
if (!started && line.contains("Opened Control listener")) {
success.add(true);
started = true;
}
}
stdout.close();
// If the control port wasn't opened, startup has failed
if (!started) success.add(false);
// Wait for the process to exit
try {
int exit = torProcess.waitFor();
if (LOG.isLoggable(INFO)) LOG.info("Tor exited with value " + exit);
} catch (InterruptedException e1) {
LOG.warning("Interrupted while waiting for Tor to exit");
Thread.currentThread().interrupt();
}
});
// Wait for the startup result
if (!success.take()) throw new IOException();
} }
@Override @Override
public HiddenServiceProperties publishHiddenService(int localPort, public HiddenServiceProperties publishHiddenService(int localPort,
int remotePort, @Nullable String privKey) throws IOException { int remotePort, @Nullable String privKey) throws IOException {
Map<Integer, String> portLines = Map<Integer, String> portLines = singletonMap(remotePort, "127.0.0.1:" + localPort);
singletonMap(remotePort, "127.0.0.1:" + localPort);
// Use the control connection to set up the hidden service // Use the control connection to set up the hidden service
Map<String, String> response; Map<String, String> response;
if (privKey == null) { if (privKey == null) {
response = getControlConnection().addOnion("NEW:ED25519-V3", response = getControlConnection().addOnion("NEW:ED25519-V3", portLines, null);
portLines, null);
} else { } else {
response = getControlConnection().addOnion(privKey, portLines); response = getControlConnection().addOnion(privKey, portLines);
} }
...@@ -362,14 +381,23 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -362,14 +381,23 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
@Override @Override
public void stop() throws IOException { public void stop() throws IOException, InterruptedException {
state.setStopped(); state.setStopping();
if (controlSocket != null && controlConnection != null) { try {
LOG.info("Stopping Tor"); if (controlConnection != null) {
try {
controlConnection.shutdownTor("TERM"); controlConnection.shutdownTor("TERM");
}
} finally {
controlConnection = null;
tryToClose(controlSocket, LOG, WARNING);
controlSocket = null;
try {
if (torProcess != null) {
torProcess.waitFor();
}
} finally { } finally {
tryToClose(controlSocket, LOG, WARNING); torProcess = null;
state.setStopped();
} }
} }
} }
...@@ -536,6 +564,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -536,6 +564,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
return controlConnection; return controlConnection;
} }
private enum ProcessState {
NOT_STARTED, STARTING, STARTED, STOPPING, STOPPED
}
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
private class NetworkState { private class NetworkState {
...@@ -545,9 +577,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -545,9 +577,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private Observer observer = null; private Observer observer = null;
@GuardedBy("this") @GuardedBy("this")
private boolean started = false, private ProcessState processState = ProcessState.NOT_STARTED;
stopped = false,
networkInitialised = false, @GuardedBy("this")
private boolean networkInitialised = false,
networkEnabled = false, networkEnabled = false,
paddingEnabled = false, paddingEnabled = false,
ipv6Enabled = false, ipv6Enabled = false,
...@@ -566,8 +599,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -566,8 +599,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
@Nullable @Nullable
private TorState state = null; private TorState state = null;
private synchronized void setObserver( private synchronized void setObserver(@Nullable Observer observer) {
@Nullable Observer observer) {
this.observer = observer; this.observer = observer;
} }
...@@ -583,18 +615,50 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -583,18 +615,50 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
} }
private synchronized void setStarting() {
// It's legal to call start() if the wrapper has never been started, or has been
// started and then stopped
if (processState != ProcessState.NOT_STARTED && processState != ProcessState.STOPPED) {
throw new IllegalStateException();
}
processState = ProcessState.STARTING;
updateState();
}
private synchronized void setStarted() { private synchronized void setStarted() {
started = true; // It's illegal to call start() and stop() concurrently
if (processState != ProcessState.STARTING) throw new IllegalStateException();
processState = ProcessState.STARTED;
updateState(); updateState();
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
private synchronized boolean isTorRunning() { private synchronized boolean isTorRunning() {
return started && !stopped; return processState == ProcessState.STARTED;
}
private synchronized void setStopping() {
// It's legal to call stop() if start() has returned or thrown an exception
if (processState != ProcessState.STARTING && processState != ProcessState.STARTED) {
throw new IllegalStateException();
}
processState = ProcessState.STOPPING;
updateState();
} }
private synchronized void setStopped() { private synchronized void setStopped() {
stopped = true; // It's illegal to call start() and stop() concurrently
if (processState != ProcessState.STOPPING) throw new IllegalStateException();
processState = ProcessState.STOPPED;
// Reset all state related to the process that has stopped
networkInitialised = false;
networkEnabled = false;
paddingEnabled = false;
ipv6Enabled = false;
circuitBuilt = false;
bootstrapPercentage = 0;
bridges = emptyList();
orConnectionsConnected = 0;
updateState(); updateState();
} }
...@@ -668,8 +732,11 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { ...@@ -668,8 +732,11 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
private synchronized TorState getState() { private synchronized TorState getState() {
if (!started || stopped) return STARTING_STOPPING; if (processState == ProcessState.NOT_STARTED) return NOT_STARTED;
if (!networkInitialised) return CONNECTING; if (processState == ProcessState.STARTING) return STARTING;
if (processState == ProcessState.STOPPING) return STOPPING;
if (processState == ProcessState.STOPPED) return STOPPED;
if (!networkInitialised) return STARTED;
if (!networkEnabled) return DISABLED; if (!networkEnabled) return DISABLED;
return bootstrapPercentage == 100 && circuitBuilt return bootstrapPercentage == 100 && circuitBuilt
&& orConnectionsConnected > 0 ? CONNECTED : CONNECTING; && orConnectionsConnected > 0 ? CONNECTED : CONNECTING;
......
...@@ -17,19 +17,33 @@ public interface TorWrapper { ...@@ -17,19 +17,33 @@ public interface TorWrapper {
Logger LOG = getLogger(TorWrapper.class.getName()); Logger LOG = getLogger(TorWrapper.class.getName());
/** /**
* Starts the Tor process, but does not yet connect to the Tor Network. * Starts the Tor process, but does not yet connect to the Tor network.
* Call {@link #enableNetwork(boolean)} for this. * Call {@link #enableNetwork(boolean)} for this.
* <p> * <p>
* This method must only be called once. To restart the Tor process, stop * This method waits for the Tor process to start before returning. Methods
* this wrapper instance and then create a new instance. * that modify the wrapper's configuration
* ({@link #publishHiddenService(int, int, String)},
* {@link #removeHiddenService(String)}, {@link #enableNetwork(boolean)},
* {@link #enableBridges(List)}, {@link #enableConnectionPadding(boolean)},
* {@link #enableIpv6(boolean)}) should be called after this method returns.
* <p>
* Do not call this method concurrently with {@link #stop()}.
* <p>
* If this method throws an exception, call {@link #stop()} before trying
* to call this method again.
*/ */
void start() throws IOException, InterruptedException; void start() throws IOException, InterruptedException;
/** /**
* Tell the Tor process to stop and returns without waiting for the * Tell the Tor process to stop and waits for it to stop before returning.
* process to exit. * <p>
* The wrapper's configuration is reset, so if the wrapper is reused by
* calling {@link #start()} again then any configuration applied via
* {@link #enableNetwork(boolean)} etc must be applied again.
* <p>
* Do not call this method concurrently with {@link #start()}.
*/ */
void stop() throws IOException; void stop() throws IOException, InterruptedException;
/** /**
* Sets an observer for observing the state of the wrapper, replacing any * Sets an observer for observing the state of the wrapper, replacing any
...@@ -111,9 +125,26 @@ public interface TorWrapper { ...@@ -111,9 +125,26 @@ public interface TorWrapper {
enum TorState { enum TorState {
/** /**
* The Tor process is either starting or stopping. * The wrapper has been created but the {@link #start()} method has not
* yet been called. This is the initial state.
*/
NOT_STARTED,
/**
* The {@link #start()} method has been called and the Tor process is
* starting.
*/
STARTING,
/**
* The {@link #start()} method has been called and the Tor process has
* started.
* <p>
* No connections to the Tor network will be made in this state. The
* wrapper remains in this state until {@link #enableNetwork(boolean)}
* is called.
*/ */
STARTING_STOPPING, STARTED,
/** /**
* The Tor process has started, its network connection is enabled, and * The Tor process has started, its network connection is enabled, and
...@@ -131,7 +162,22 @@ public interface TorWrapper { ...@@ -131,7 +162,22 @@ public interface TorWrapper {
/** /**
* The Tor process has started but its network connection is disabled. * The Tor process has started but its network connection is disabled.
*/ */
DISABLED DISABLED,
/**
* The {@link #stop()} method has been called and the Tor process is
* stopping.
*/
STOPPING,
/**
* The {@link #stop()} method has been called and the Tor process has
* stopped.
* <p>
* A new Tor process can be started by calling the {@link #start()}
* method again.
*/
STOPPED
} }
/** /**
......
...@@ -20,14 +20,12 @@ abstract class JavaTorWrapper extends AbstractTorWrapper { ...@@ -20,14 +20,12 @@ abstract class JavaTorWrapper extends AbstractTorWrapper {
File torDirectory, File torDirectory,
int torSocksPort, int torSocksPort,
int torControlPort) { int torControlPort) {
super(ioExecutor, eventExecutor, architecture, torDirectory, super(ioExecutor, eventExecutor, architecture, torDirectory, torSocksPort, torControlPort);
torSocksPort, torControlPort);
} }
@Override @Override
protected long getLastUpdateTime() { protected long getLastUpdateTime() {
CodeSource codeSource = CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
getClass().getProtectionDomain().getCodeSource();
if (codeSource == null) throw new AssertionError("CodeSource null"); if (codeSource == null) throw new AssertionError("CodeSource null");
try { try {
URI path = codeSource.getLocation().toURI(); URI path = codeSource.getLocation().toURI();
...@@ -39,8 +37,7 @@ abstract class JavaTorWrapper extends AbstractTorWrapper { ...@@ -39,8 +37,7 @@ abstract class JavaTorWrapper extends AbstractTorWrapper {
} }
@Override @Override
protected InputStream getResourceInputStream(String name, protected InputStream getResourceInputStream(String name, String extension) {
String extension) {
ClassLoader cl = getClass().getClassLoader(); ClassLoader cl = getClass().getClassLoader();
return requireNonNull(cl.getResourceAsStream(name + extension)); return requireNonNull(cl.getResourceAsStream(name + extension));
} }
......
...@@ -5,14 +5,8 @@ import com.sun.jna.platform.win32.Kernel32; ...@@ -5,14 +5,8 @@ import com.sun.jna.platform.win32.Kernel32;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import static java.util.logging.Level.INFO;
/** /**
* A Tor wrapper for the Windows operating system. * A Tor wrapper for the Windows operating system.
*/ */
...@@ -40,8 +34,7 @@ public class WindowsTorWrapper extends JavaTorWrapper { ...@@ -40,8 +34,7 @@ public class WindowsTorWrapper extends JavaTorWrapper {
File torDirectory, File torDirectory,
int torSocksPort, int torSocksPort,
int torControlPort) { int torControlPort) {
super(ioExecutor, eventExecutor, architecture, torDirectory, super(ioExecutor, eventExecutor, architecture, torDirectory, torSocksPort, torControlPort);
torSocksPort, torControlPort);
} }
@Override @Override
...@@ -49,44 +42,6 @@ public class WindowsTorWrapper extends JavaTorWrapper { ...@@ -49,44 +42,6 @@ public class WindowsTorWrapper extends JavaTorWrapper {
return Kernel32.INSTANCE.GetCurrentProcessId(); return Kernel32.INSTANCE.GetCurrentProcessId();
} }
@Override
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, IOException {
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
// Wait for the control port to be opened, then continue to read its
// stdout and stderr in a background thread until it exits.
BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
ioExecutor.execute(() -> {
boolean started = false;
// Read the process's stdout (and redirected stderr)
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Startup has succeeded when the control port is open
while (stdout.hasNextLine()) {
String line = stdout.nextLine();
if (!started && line.contains("Opened Control listener")) {
success.add(true);
started = true;
}
}
stdout.close();
// If the control port wasn't opened, startup has failed
if (!started) success.add(false);
// Wait for the process to exit
try {
int exit = torProcess.waitFor();
if (LOG.isLoggable(INFO))
LOG.info("Tor exited with value " + exit);
} catch (InterruptedException e1) {
LOG.warning("Interrupted while waiting for Tor to exit");
Thread.currentThread().interrupt();
}
});
// Wait for the startup result
if (!success.take()) throw new IOException();
}
@Override @Override
protected String getExecutableExtension() { protected String getExecutableExtension() {
return ".exe"; return ".exe";
......
...@@ -18,6 +18,9 @@ import static org.briarproject.onionwrapper.TestUtils.getTestDirectory; ...@@ -18,6 +18,9 @@ import static org.briarproject.onionwrapper.TestUtils.getTestDirectory;
import static org.briarproject.onionwrapper.TestUtils.isLinux; import static org.briarproject.onionwrapper.TestUtils.isLinux;
import static org.briarproject.onionwrapper.TestUtils.isWindows; import static org.briarproject.onionwrapper.TestUtils.isWindows;
import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTED; import static org.briarproject.onionwrapper.TorWrapper.TorState.CONNECTED;
import static org.briarproject.onionwrapper.TorWrapper.TorState.STARTED;
import static org.briarproject.onionwrapper.TorWrapper.TorState.STOPPED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull; import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
...@@ -28,7 +31,7 @@ public class BootstrapTest extends BaseTest { ...@@ -28,7 +31,7 @@ public class BootstrapTest extends BaseTest {
private static final int SOCKS_PORT = 59060; private static final int SOCKS_PORT = 59060;
private static final int CONTROL_PORT = 59061; private static final int CONTROL_PORT = 59061;
private final static long TIMEOUT = MINUTES.toMillis(2); private final static long TIMEOUT = MINUTES.toMillis(5);
private final ExecutorService executor = newCachedThreadPool(); private final ExecutorService executor = newCachedThreadPool();
private final File torDir = getTestDirectory(); private final File torDir = getTestDirectory();
...@@ -59,9 +62,16 @@ public class BootstrapTest extends BaseTest { ...@@ -59,9 +62,16 @@ public class BootstrapTest extends BaseTest {
throw new AssertionError("Running on unsupported OS"); throw new AssertionError("Running on unsupported OS");
} }
// Bootstrap twice with the same wrapper to test wrapper reuse
testBootstrapping(tor);
testBootstrapping(tor);
}
private void testBootstrapping(TorWrapper tor) throws Exception {
boolean connected; boolean connected;
try { try {
tor.start(); tor.start();
assertEquals(STARTED, tor.getTorState());
tor.enableNetwork(true); tor.enableNetwork(true);
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < TIMEOUT) { while (System.currentTimeMillis() - start < TIMEOUT) {
...@@ -74,6 +84,7 @@ public class BootstrapTest extends BaseTest { ...@@ -74,6 +84,7 @@ public class BootstrapTest extends BaseTest {
else LOG.warning("Could not connect to Tor within timeout"); else LOG.warning("Could not connect to Tor within timeout");
} finally { } finally {
tor.stop(); tor.stop();
assertEquals(STOPPED, tor.getTorState());
} }
assertTrue(connected); assertTrue(connected);
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment