mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Factor out Tor wrapper from plugin
This commit is contained in:
@@ -5,6 +5,7 @@ import android.app.Application;
|
|||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
@@ -16,8 +17,9 @@ import org.briarproject.bramble.api.plugin.TorSocksPort;
|
|||||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.AndroidTorWrapper;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -28,6 +30,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
|
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -39,13 +42,13 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
|
@EventExecutor Executor eventExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
SocketFactory torSocketFactory,
|
SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory,
|
BackoffFactory backoffFactory,
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
@@ -55,8 +58,8 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
|||||||
@TorControlPort int torControlPort,
|
@TorControlPort int torControlPort,
|
||||||
Application app,
|
Application app,
|
||||||
AndroidWakeLockManager wakeLockManager) {
|
AndroidWakeLockManager wakeLockManager) {
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||||
circumventionProvider, batteryManager, clock, crypto,
|
circumventionProvider, batteryManager, clock, crypto,
|
||||||
torDirectory, torSocksPort, torControlPort);
|
torDirectory, torSocksPort, torControlPort);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
@@ -79,12 +82,18 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
|||||||
TorPlugin createPluginInstance(Backoff backoff,
|
TorPlugin createPluginInstance(Backoff backoff,
|
||||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||||
String architecture) {
|
String architecture) {
|
||||||
return new AndroidTorPlugin(ioExecutor,
|
TorWrapper tor = new AndroidTorWrapper(app, wakeLockManager,
|
||||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
ioExecutor, eventExecutor, architecture, torDirectory,
|
||||||
torSocketFactory, clock, resourceProvider,
|
torSocksPort, torControlPort);
|
||||||
circumventionProvider, batteryManager, wakeLockManager,
|
// Android versions 7.1 and newer can verify Let's Encrypt TLS certs
|
||||||
backoff, torRendezvousCrypto, callback, architecture,
|
// signed with the IdentTrust DST Root X3 certificate. Older versions
|
||||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
// of Android consider the certificate to have expired at the end of
|
||||||
torControlPort);
|
// September 2021.
|
||||||
|
boolean canVerifyLetsEncryptCerts = SDK_INT >= 25;
|
||||||
|
return new TorPlugin(ioExecutor, wakefulIoExecutor,
|
||||||
|
networkManager, locationUtils, torSocketFactory,
|
||||||
|
circumventionProvider, batteryManager, backoff,
|
||||||
|
torRendezvousCrypto, tor, callback, MAX_LATENCY,
|
||||||
|
MAX_IDLE_TIME, canVerifyLetsEncryptCerts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,36 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
package org.briarproject.bramble.plugin.tor.wrapper;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.pm.PackageInfo;
|
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.os.Build;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidWakeLock;
|
import org.briarproject.bramble.api.system.AndroidWakeLock;
|
||||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
|
||||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
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 javax.net.SocketFactory;
|
|
||||||
|
|
||||||
import androidx.annotation.ChecksSdkIntAtLeast;
|
|
||||||
|
|
||||||
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;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@NotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
public class AndroidTorWrapper extends AbstractTorWrapper {
|
||||||
class AndroidTorPlugin extends TorPlugin {
|
|
||||||
|
|
||||||
private static final List<String> LIBRARY_ARCHITECTURES =
|
private static final List<String> LIBRARY_ARCHITECTURES =
|
||||||
asList("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
|
asList("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
|
||||||
@@ -50,37 +40,22 @@ class AndroidTorPlugin extends TorPlugin {
|
|||||||
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(AndroidTorPlugin.class.getName());
|
getLogger(AndroidTorWrapper.class.getName());
|
||||||
|
|
||||||
private final Application app;
|
private final Application app;
|
||||||
private final AndroidWakeLock wakeLock;
|
private final AndroidWakeLock wakeLock;
|
||||||
private final File torLib, obfs4Lib, snowflakeLib;
|
private final File torLib, obfs4Lib, snowflakeLib;
|
||||||
|
|
||||||
AndroidTorPlugin(Executor ioExecutor,
|
public AndroidTorWrapper(Application app,
|
||||||
Executor wakefulIoExecutor,
|
|
||||||
Application app,
|
|
||||||
NetworkManager networkManager,
|
|
||||||
LocationUtils locationUtils,
|
|
||||||
SocketFactory torSocketFactory,
|
|
||||||
Clock clock,
|
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
|
||||||
BatteryManager batteryManager,
|
|
||||||
AndroidWakeLockManager wakeLockManager,
|
AndroidWakeLockManager wakeLockManager,
|
||||||
Backoff backoff,
|
Executor ioExecutor,
|
||||||
TorRendezvousCrypto torRendezvousCrypto,
|
Executor eventExecutor,
|
||||||
PluginCallback callback,
|
|
||||||
String architecture,
|
String architecture,
|
||||||
long maxLatency,
|
|
||||||
int maxIdleTime,
|
|
||||||
File torDirectory,
|
File torDirectory,
|
||||||
int torSocksPort,
|
int torSocksPort,
|
||||||
int torControlPort) {
|
int torControlPort) {
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
super(ioExecutor, eventExecutor, architecture, torDirectory,
|
||||||
torSocketFactory, clock, resourceProvider,
|
torSocksPort, torControlPort);
|
||||||
circumventionProvider, batteryManager, backoff,
|
|
||||||
torRendezvousCrypto, callback, architecture, maxLatency,
|
|
||||||
maxIdleTime, torDirectory, 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;
|
||||||
@@ -106,22 +81,30 @@ class AndroidTorPlugin extends TorPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void enableNetwork(boolean enable) throws IOException {
|
public InputStream getResourceInputStream(String name, String extension) {
|
||||||
|
Resources res = app.getResources();
|
||||||
|
// Extension is ignored on Android, resources are retrieved without it
|
||||||
|
int resId = res.getIdentifier(name, "raw", app.getPackageName());
|
||||||
|
return res.openRawResource(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableNetwork(boolean enable) throws IOException {
|
||||||
if (enable) wakeLock.acquire();
|
if (enable) wakeLock.acquire();
|
||||||
super.enableNetwork(enable);
|
try {
|
||||||
if (!enable) wakeLock.release();
|
super.enableNetwork(enable);
|
||||||
|
} finally {
|
||||||
|
if (!enable) wakeLock.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ChecksSdkIntAtLeast(api = 25)
|
public void stop() throws IOException {
|
||||||
protected boolean canVerifyLetsEncryptCerts() {
|
try {
|
||||||
return SDK_INT >= 25;
|
super.stop();
|
||||||
}
|
} finally {
|
||||||
|
wakeLock.release();
|
||||||
@Override
|
}
|
||||||
public void stop() {
|
|
||||||
super.stop();
|
|
||||||
wakeLock.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,8 +119,8 @@ class AndroidTorPlugin extends TorPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected File getSnowflakeExecutableFile() {
|
protected File getSnowflakeExecutableFile() {
|
||||||
return snowflakeLib.exists()
|
return snowflakeLib.exists() ? snowflakeLib
|
||||||
? snowflakeLib : super.getSnowflakeExecutableFile();
|
: super.getSnowflakeExecutableFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -184,6 +167,7 @@ class AndroidTorPlugin extends TorPlugin {
|
|||||||
}
|
}
|
||||||
List<String> libPaths = getSupportedLibraryPaths(libName);
|
List<String> libPaths = getSupportedLibraryPaths(libName);
|
||||||
for (File apk : findApkFiles(sourceDir)) {
|
for (File apk : findApkFiles(sourceDir)) {
|
||||||
|
@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()) {
|
||||||
@@ -228,11 +212,22 @@ class AndroidTorPlugin extends TorPlugin {
|
|||||||
*/
|
*/
|
||||||
private List<String> getSupportedLibraryPaths(String libName) {
|
private List<String> getSupportedLibraryPaths(String libName) {
|
||||||
List<String> architectures = new ArrayList<>();
|
List<String> architectures = new ArrayList<>();
|
||||||
for (String abi : AndroidUtils.getSupportedArchitectures()) {
|
for (String abi : getSupportedArchitectures()) {
|
||||||
if (LIBRARY_ARCHITECTURES.contains(abi)) {
|
if (LIBRARY_ARCHITECTURES.contains(abi)) {
|
||||||
architectures.add("lib/" + abi + "/" + libName);
|
architectures.add("lib/" + abi + "/" + libName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return architectures;
|
return architectures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Collection<String> getSupportedArchitectures() {
|
||||||
|
List<String> abis = new ArrayList<>();
|
||||||
|
if (SDK_INT >= 21) {
|
||||||
|
abis.addAll(asList(Build.SUPPORTED_ABIS));
|
||||||
|
} else {
|
||||||
|
abis.add(Build.CPU_ABI);
|
||||||
|
if (Build.CPU_ABI2 != null) abis.add(Build.CPU_ABI2);
|
||||||
|
}
|
||||||
|
return abis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -40,9 +40,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -288,8 +288,10 @@ class PluginManagerImpl implements PluginManager, Service {
|
|||||||
private class Callback implements PluginCallback {
|
private class Callback implements PluginCallback {
|
||||||
|
|
||||||
private final TransportId id;
|
private final TransportId id;
|
||||||
private final AtomicReference<State> state =
|
private final Object stateLock = new Object();
|
||||||
new AtomicReference<>(STARTING_STOPPING);
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private State state = STARTING_STOPPING;
|
||||||
|
|
||||||
private Callback(TransportId id) {
|
private Callback(TransportId id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -343,22 +345,26 @@ class PluginManagerImpl implements PluginManager, Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pluginStateChanged(State newState) {
|
public void pluginStateChanged(State newState) {
|
||||||
State oldState = state.getAndSet(newState);
|
synchronized (stateLock) {
|
||||||
if (newState != oldState) {
|
if (newState != state) {
|
||||||
if (LOG.isLoggable(INFO)) {
|
State oldState = state;
|
||||||
LOG.info(id + " changed from state " + oldState
|
state = newState;
|
||||||
+ " to " + newState);
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(id + " changed from state " + oldState
|
||||||
|
+ " to " + newState);
|
||||||
|
}
|
||||||
|
eventBus.broadcast(new TransportStateEvent(id, newState));
|
||||||
|
if (newState == ACTIVE) {
|
||||||
|
eventBus.broadcast(new TransportActiveEvent(id));
|
||||||
|
} else if (oldState == ACTIVE) {
|
||||||
|
eventBus.broadcast(new TransportInactiveEvent(id));
|
||||||
|
}
|
||||||
|
} else if (newState == DISABLED) {
|
||||||
|
// Broadcast an event even though the state hasn't changed,
|
||||||
|
// as the reasons for the plugin being disabled may have
|
||||||
|
// changed
|
||||||
|
eventBus.broadcast(new TransportStateEvent(id, newState));
|
||||||
}
|
}
|
||||||
eventBus.broadcast(new TransportStateEvent(id, newState));
|
|
||||||
if (newState == ACTIVE) {
|
|
||||||
eventBus.broadcast(new TransportActiveEvent(id));
|
|
||||||
} else if (oldState == ACTIVE) {
|
|
||||||
eventBus.broadcast(new TransportInactiveEvent(id));
|
|
||||||
}
|
|
||||||
} else if (newState == DISABLED) {
|
|
||||||
// Broadcast an event even though the state hasn't changed, as
|
|
||||||
// the reasons for the plugin being disabled may have changed
|
|
||||||
eventBus.broadcast(new TransportStateEvent(id, newState));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
import net.freehaven.tor.control.EventHandler;
|
|
||||||
import net.freehaven.tor.control.TorControlConnection;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.PoliteExecutor;
|
import org.briarproject.bramble.PoliteExecutor;
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
@@ -27,30 +24,21 @@ import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
|
|||||||
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
|
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
||||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.HiddenServiceProperties;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState;
|
||||||
|
import org.briarproject.nullsafety.InterfaceNotNullByDefault;
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Scanner;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -63,13 +51,9 @@ import javax.net.SocketFactory;
|
|||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
|
||||||
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
|
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
|
||||||
@@ -94,33 +78,16 @@ import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DAT
|
|||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
|
||||||
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
|
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
|
||||||
import static org.briarproject.bramble.util.StringUtils.UTF_8;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@InterfaceNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
class TorPlugin implements DuplexPlugin, EventListener {
|
||||||
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|
||||||
|
|
||||||
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
|
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private static final String[] EVENTS = {
|
|
||||||
"CIRC",
|
|
||||||
"ORCONN",
|
|
||||||
"STATUS_GENERAL",
|
|
||||||
"STATUS_CLIENT",
|
|
||||||
"HS_DESC",
|
|
||||||
"NOTICE",
|
|
||||||
"WARN",
|
|
||||||
"ERR"
|
|
||||||
};
|
|
||||||
private static final String OWNER = "__OwningControllerProcess";
|
|
||||||
private static final int COOKIE_TIMEOUT_MS = 3000;
|
|
||||||
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
|
||||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
||||||
|
|
||||||
protected final Executor ioExecutor;
|
protected final Executor ioExecutor;
|
||||||
@@ -129,91 +96,63 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private final NetworkManager networkManager;
|
private final NetworkManager networkManager;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final SocketFactory torSocketFactory;
|
private final SocketFactory torSocketFactory;
|
||||||
private final Clock clock;
|
private final CircumventionProvider circumventionProvider;
|
||||||
private final BatteryManager batteryManager;
|
private final BatteryManager batteryManager;
|
||||||
private final Backoff backoff;
|
private final Backoff backoff;
|
||||||
private final TorRendezvousCrypto torRendezvousCrypto;
|
private final TorRendezvousCrypto torRendezvousCrypto;
|
||||||
|
private final TorWrapper tor;
|
||||||
private final PluginCallback callback;
|
private final PluginCallback callback;
|
||||||
private final String architecture;
|
|
||||||
private final CircumventionProvider circumventionProvider;
|
|
||||||
private final ResourceProvider resourceProvider;
|
|
||||||
private final long maxLatency;
|
private final long maxLatency;
|
||||||
private final int maxIdleTime;
|
private final int maxIdleTime;
|
||||||
|
private final boolean canVerifyLetsEncryptCerts;
|
||||||
private final int socketTimeout;
|
private final int socketTimeout;
|
||||||
private final File torDirectory;
|
|
||||||
private final File configFile;
|
|
||||||
private final int torSocksPort;
|
|
||||||
private final int torControlPort;
|
|
||||||
private final File doneFile, cookieFile;
|
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
protected final PluginState state = new PluginState();
|
protected final PluginState state = new PluginState();
|
||||||
|
|
||||||
private volatile Socket controlSocket = null;
|
|
||||||
private volatile TorControlConnection controlConnection = null;
|
|
||||||
private volatile Settings settings = null;
|
private volatile Settings settings = null;
|
||||||
|
|
||||||
protected abstract int getProcessId();
|
|
||||||
|
|
||||||
protected abstract long getLastUpdateTime();
|
|
||||||
|
|
||||||
TorPlugin(Executor ioExecutor,
|
TorPlugin(Executor ioExecutor,
|
||||||
Executor wakefulIoExecutor,
|
Executor wakefulIoExecutor,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
SocketFactory torSocketFactory,
|
SocketFactory torSocketFactory,
|
||||||
Clock clock,
|
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
Backoff backoff,
|
Backoff backoff,
|
||||||
TorRendezvousCrypto torRendezvousCrypto,
|
TorRendezvousCrypto torRendezvousCrypto,
|
||||||
|
TorWrapper tor,
|
||||||
PluginCallback callback,
|
PluginCallback callback,
|
||||||
String architecture,
|
|
||||||
long maxLatency,
|
long maxLatency,
|
||||||
int maxIdleTime,
|
int maxIdleTime,
|
||||||
File torDirectory,
|
boolean canVerifyLetsEncryptCerts) {
|
||||||
int torSocksPort,
|
|
||||||
int torControlPort) {
|
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||||
this.networkManager = networkManager;
|
this.networkManager = networkManager;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.torSocketFactory = torSocketFactory;
|
this.torSocketFactory = torSocketFactory;
|
||||||
this.clock = clock;
|
|
||||||
this.resourceProvider = resourceProvider;
|
|
||||||
this.circumventionProvider = circumventionProvider;
|
this.circumventionProvider = circumventionProvider;
|
||||||
this.batteryManager = batteryManager;
|
this.batteryManager = batteryManager;
|
||||||
this.backoff = backoff;
|
this.backoff = backoff;
|
||||||
this.torRendezvousCrypto = torRendezvousCrypto;
|
this.torRendezvousCrypto = torRendezvousCrypto;
|
||||||
|
this.tor = tor;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.architecture = architecture;
|
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
this.maxIdleTime = maxIdleTime;
|
this.maxIdleTime = maxIdleTime;
|
||||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
this.canVerifyLetsEncryptCerts = canVerifyLetsEncryptCerts;
|
||||||
|
if (maxIdleTime > Integer.MAX_VALUE / 2) {
|
||||||
socketTimeout = Integer.MAX_VALUE;
|
socketTimeout = Integer.MAX_VALUE;
|
||||||
else socketTimeout = maxIdleTime * 2;
|
} else {
|
||||||
this.torDirectory = torDirectory;
|
socketTimeout = maxIdleTime * 2;
|
||||||
this.torSocksPort = torSocksPort;
|
}
|
||||||
this.torControlPort = torControlPort;
|
|
||||||
configFile = new File(torDirectory, "torrc");
|
|
||||||
doneFile = new File(torDirectory, "done");
|
|
||||||
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
|
||||||
// Don't execute more than one connection status check at a time
|
// Don't execute more than one connection status check at a time
|
||||||
connectionStatusExecutor =
|
connectionStatusExecutor =
|
||||||
new PoliteExecutor("TorPlugin", ioExecutor, 1);
|
new PoliteExecutor("TorPlugin", ioExecutor, 1);
|
||||||
}
|
tor.setStateObserver(torState -> {
|
||||||
|
State s = state.getState(torState);
|
||||||
protected File getTorExecutableFile() {
|
if (s == ACTIVE) backoff.reset();
|
||||||
return new File(torDirectory, "tor");
|
callback.pluginStateChanged(s);
|
||||||
}
|
});
|
||||||
|
|
||||||
protected File getObfs4ExecutableFile() {
|
|
||||||
return new File(torDirectory, "obfs4proxy");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected File getSnowflakeExecutableFile() {
|
|
||||||
return new File(torDirectory, "snowflake");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -234,89 +173,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void start() throws PluginException {
|
public void start() throws PluginException {
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
if (!torDirectory.exists()) {
|
|
||||||
if (!torDirectory.mkdirs()) {
|
|
||||||
LOG.warning("Could not create Tor directory.");
|
|
||||||
throw new PluginException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Load the settings
|
// Load the settings
|
||||||
settings = callback.getSettings();
|
settings = callback.getSettings();
|
||||||
|
// Start Tor
|
||||||
try {
|
try {
|
||||||
// Install or update the assets if necessary
|
tor.start();
|
||||||
if (!assetsAreUpToDate()) installAssets();
|
|
||||||
// Start from the default config every time
|
|
||||||
extract(getConfigInputStream(), configFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new PluginException(e);
|
|
||||||
}
|
|
||||||
if (cookieFile.exists() && !cookieFile.delete())
|
|
||||||
LOG.warning("Old auth cookie not deleted");
|
|
||||||
// Start a new Tor process
|
|
||||||
LOG.info("Starting Tor");
|
|
||||||
File torFile = getTorExecutableFile();
|
|
||||||
String torPath = torFile.getAbsolutePath();
|
|
||||||
String configPath = configFile.getAbsolutePath();
|
|
||||||
String pid = String.valueOf(getProcessId());
|
|
||||||
Process torProcess;
|
|
||||||
ProcessBuilder pb =
|
|
||||||
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
|
|
||||||
Map<String, String> env = pb.environment();
|
|
||||||
env.put("HOME", torDirectory.getAbsolutePath());
|
|
||||||
pb.directory(torDirectory);
|
|
||||||
pb.redirectErrorStream(true);
|
|
||||||
try {
|
|
||||||
torProcess = pb.start();
|
|
||||||
} catch (SecurityException | IOException e) {
|
|
||||||
throw new PluginException(e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Wait for the Tor process to start
|
|
||||||
waitForTorToStart(torProcess);
|
|
||||||
// Wait for the auth cookie file to be created/updated
|
|
||||||
long start = clock.currentTimeMillis();
|
|
||||||
while (cookieFile.length() < 32) {
|
|
||||||
if (clock.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
|
|
||||||
LOG.warning("Auth cookie not created");
|
|
||||||
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
|
|
||||||
throw new PluginException();
|
|
||||||
}
|
|
||||||
//noinspection BusyWait
|
|
||||||
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
|
|
||||||
}
|
|
||||||
LOG.info("Auth cookie created");
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.warning("Interrupted while starting Tor");
|
LOG.warning("Interrupted while starting Tor");
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
throw new PluginException();
|
throw new PluginException();
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Open a control connection and authenticate using the cookie file
|
|
||||||
controlSocket = new Socket("127.0.0.1", torControlPort);
|
|
||||||
controlConnection = new TorControlConnection(controlSocket);
|
|
||||||
controlConnection.authenticate(read(cookieFile));
|
|
||||||
// Tell Tor to exit when the control connection is closed
|
|
||||||
controlConnection.takeOwnership();
|
|
||||||
controlConnection.resetConf(singletonList(OWNER));
|
|
||||||
// Register to receive events from the Tor process
|
|
||||||
controlConnection.setEventHandler(this);
|
|
||||||
controlConnection.setEvents(asList(EVENTS));
|
|
||||||
// Check whether Tor has already bootstrapped
|
|
||||||
String info = controlConnection.getInfo("status/bootstrap-phase");
|
|
||||||
if (info != null && info.contains("PROGRESS=100")) {
|
|
||||||
LOG.info("Tor has already bootstrapped");
|
|
||||||
state.setBootstrapped();
|
|
||||||
}
|
|
||||||
// Check whether Tor has already built a circuit
|
|
||||||
info = controlConnection.getInfo("status/circuit-established");
|
|
||||||
if ("1".equals(info)) {
|
|
||||||
LOG.info("Tor has already built a circuit");
|
|
||||||
state.setCircuitBuilt(true);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PluginException(e);
|
throw new PluginException(e);
|
||||||
}
|
}
|
||||||
state.setStarted();
|
|
||||||
// Check whether we're online
|
// Check whether we're online
|
||||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||||
batteryManager.isCharging());
|
batteryManager.isCharging());
|
||||||
@@ -324,130 +192,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
bind();
|
bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean assetsAreUpToDate() {
|
|
||||||
return doneFile.lastModified() > getLastUpdateTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void installAssets() throws IOException {
|
|
||||||
// The done file may already exist from a previous installation
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
doneFile.delete();
|
|
||||||
installTorExecutable();
|
|
||||||
installObfs4Executable();
|
|
||||||
installSnowflakeExecutable();
|
|
||||||
if (!doneFile.createNewFile())
|
|
||||||
LOG.warning("Failed to create done file");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void extract(InputStream in, File dest) throws IOException {
|
|
||||||
OutputStream out = new FileOutputStream(dest);
|
|
||||||
copyAndClose(in, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void installTorExecutable() throws IOException {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Installing Tor binary for " + architecture);
|
|
||||||
File torFile = getTorExecutableFile();
|
|
||||||
extract(getExecutableInputStream("tor"), torFile);
|
|
||||||
if (!torFile.setExecutable(true, true)) throw new IOException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void installObfs4Executable() throws IOException {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Installing obfs4proxy binary for " + architecture);
|
|
||||||
File obfs4File = getObfs4ExecutableFile();
|
|
||||||
extract(getExecutableInputStream("obfs4proxy"), obfs4File);
|
|
||||||
if (!obfs4File.setExecutable(true, true)) throw new IOException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void installSnowflakeExecutable() throws IOException {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Installing snowflake binary for " + architecture);
|
|
||||||
File snowflakeFile = getSnowflakeExecutableFile();
|
|
||||||
extract(getExecutableInputStream("snowflake"), snowflakeFile);
|
|
||||||
if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private InputStream getExecutableInputStream(String basename) {
|
|
||||||
String ext = getExecutableExtension();
|
|
||||||
return requireNonNull(resourceProvider
|
|
||||||
.getResourceInputStream(architecture + "/" + basename, ext));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getExecutableExtension() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void append(StringBuilder strb, String name, Object value) {
|
|
||||||
strb.append(name);
|
|
||||||
strb.append(" ");
|
|
||||||
strb.append(value);
|
|
||||||
strb.append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
private InputStream getConfigInputStream() {
|
|
||||||
File dataDirectory = new File(torDirectory, ".tor");
|
|
||||||
StringBuilder strb = new StringBuilder();
|
|
||||||
append(strb, "ControlPort", torControlPort);
|
|
||||||
append(strb, "CookieAuthentication", 1);
|
|
||||||
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
|
|
||||||
append(strb, "DisableNetwork", 1);
|
|
||||||
append(strb, "RunAsDaemon", 1);
|
|
||||||
append(strb, "SafeSocks", 1);
|
|
||||||
append(strb, "SocksPort", torSocksPort);
|
|
||||||
strb.append("GeoIPFile\n");
|
|
||||||
strb.append("GeoIPv6File\n");
|
|
||||||
append(strb, "ConnectionPadding", 0);
|
|
||||||
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
|
|
||||||
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
|
|
||||||
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
|
|
||||||
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
|
|
||||||
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
|
|
||||||
return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void listFiles(File f) {
|
|
||||||
if (f.isDirectory()) {
|
|
||||||
File[] children = f.listFiles();
|
|
||||||
if (children != null) for (File child : children) listFiles(child);
|
|
||||||
} else {
|
|
||||||
LOG.info(f.getAbsolutePath() + " " + f.length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] read(File f) throws IOException {
|
|
||||||
byte[] b = new byte[(int) f.length()];
|
|
||||||
FileInputStream in = new FileInputStream(f);
|
|
||||||
try {
|
|
||||||
int offset = 0;
|
|
||||||
while (offset < b.length) {
|
|
||||||
int read = in.read(b, offset, b.length - offset);
|
|
||||||
if (read == -1) throw new EOFException();
|
|
||||||
offset += read;
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
} finally {
|
|
||||||
tryToClose(in, LOG, WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void waitForTorToStart(Process torProcess)
|
|
||||||
throws InterruptedException, PluginException {
|
|
||||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
|
||||||
// Log the first line of stdout (contains Tor and library versions)
|
|
||||||
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
|
||||||
// Read the process's stdout (and redirected stderr) until it detaches
|
|
||||||
while (stdout.hasNextLine()) stdout.nextLine();
|
|
||||||
stdout.close();
|
|
||||||
// Wait for the process to detach or exit
|
|
||||||
int exit = torProcess.waitFor();
|
|
||||||
if (exit != 0) {
|
|
||||||
if (LOG.isLoggable(WARNING))
|
|
||||||
LOG.warning("Tor exited with value " + exit);
|
|
||||||
throw new PluginException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bind() {
|
private void bind() {
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
// If there's already a port number stored in config, reuse it
|
// If there's already a port number stored in config, reuse it
|
||||||
@@ -471,9 +215,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Store the port number
|
// Store the port number
|
||||||
String localPort = String.valueOf(ss.getLocalPort());
|
int localPort = ss.getLocalPort();
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.put(PREF_TOR_PORT, localPort);
|
s.put(PREF_TOR_PORT, String.valueOf(localPort));
|
||||||
callback.mergeSettings(s);
|
callback.mergeSettings(s);
|
||||||
// Create a hidden service if necessary
|
// Create a hidden service if necessary
|
||||||
ioExecutor.execute(() -> publishHiddenService(localPort));
|
ioExecutor.execute(() -> publishHiddenService(localPort));
|
||||||
@@ -483,48 +227,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publishHiddenService(String port) {
|
private void publishHiddenService(int localPort) {
|
||||||
if (!state.isTorRunning()) return;
|
if (!tor.isTorRunning()) return;
|
||||||
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
|
String privKey = settings.get(HS_PRIVATE_KEY_V3);
|
||||||
publishV3HiddenService(port, privKey3);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publishV3HiddenService(String port, @Nullable String privKey) {
|
|
||||||
LOG.info("Creating v3 hidden service");
|
LOG.info("Creating v3 hidden service");
|
||||||
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
|
HiddenServiceProperties hsProps;
|
||||||
Map<String, String> response;
|
|
||||||
try {
|
try {
|
||||||
// Use the control connection to set up the hidden service
|
hsProps = tor.publishHiddenService(localPort, 80, privKey);
|
||||||
if (privKey == null) {
|
|
||||||
response = controlConnection.addOnion("NEW:ED25519-V3",
|
|
||||||
portLines, null);
|
|
||||||
} else {
|
|
||||||
response = controlConnection.addOnion(privKey, portLines);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!response.containsKey(HS_ADDRESS)) {
|
|
||||||
LOG.warning("Tor did not return a hidden service address");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (privKey == null && !response.containsKey(HS_PRIVKEY)) {
|
|
||||||
LOG.warning("Tor did not return a private key");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String onion3 = response.get(HS_ADDRESS);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("V3 hidden service " + scrubOnion(onion3));
|
LOG.info("V3 hidden service " + scrubOnion(hsProps.onion));
|
||||||
}
|
}
|
||||||
if (privKey == null) {
|
if (privKey == null) {
|
||||||
// Publish the hidden service's onion hostname in transport props
|
// Publish the hidden service's onion hostname in transport props
|
||||||
TransportProperties p = new TransportProperties();
|
TransportProperties p = new TransportProperties();
|
||||||
p.put(PROP_ONION_V3, onion3);
|
p.put(PROP_ONION_V3, hsProps.onion);
|
||||||
callback.mergeLocalProperties(p);
|
callback.mergeLocalProperties(p);
|
||||||
// Save the hidden service's private key for next time
|
// Save the hidden service's private key for next time
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
|
s.put(HS_PRIVATE_KEY_V3, hsProps.privKey);
|
||||||
callback.mergeSettings(s);
|
callback.mergeSettings(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -547,50 +271,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void enableNetwork(boolean enable) throws IOException {
|
|
||||||
if (!state.enableNetwork(enable)) return; // Unchanged
|
|
||||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
|
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
|
|
||||||
if (bridgeTypes.isEmpty()) {
|
if (bridgeTypes.isEmpty()) {
|
||||||
controlConnection.setConf("UseBridges", "0");
|
tor.disableBridges();
|
||||||
controlConnection.resetConf(singletonList("Bridge"));
|
|
||||||
} else {
|
} else {
|
||||||
Collection<String> conf = new ArrayList<>();
|
List<String> bridges = new ArrayList<>();
|
||||||
conf.add("UseBridges 1");
|
|
||||||
boolean letsEncrypt = canVerifyLetsEncryptCerts();
|
|
||||||
for (BridgeType bridgeType : bridgeTypes) {
|
for (BridgeType bridgeType : bridgeTypes) {
|
||||||
conf.addAll(circumventionProvider
|
bridges.addAll(circumventionProvider.getBridges(bridgeType,
|
||||||
.getBridges(bridgeType, countryCode, letsEncrypt));
|
countryCode, canVerifyLetsEncryptCerts));
|
||||||
}
|
}
|
||||||
controlConnection.setConf(conf);
|
tor.enableBridges(bridges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this device can verify Let's Encrypt certificates signed
|
|
||||||
* with the IdentTrust DST Root X3 certificate, which expired at the end of
|
|
||||||
* September 2021.
|
|
||||||
*/
|
|
||||||
protected boolean canVerifyLetsEncryptCerts() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
ServerSocket ss = state.setStopped();
|
ServerSocket ss = state.setStopped();
|
||||||
tryToClose(ss, LOG, WARNING);
|
tryToClose(ss, LOG, WARNING);
|
||||||
if (controlSocket != null && controlConnection != null) {
|
try {
|
||||||
try {
|
tor.stop();
|
||||||
LOG.info("Stopping Tor");
|
} catch (IOException e) {
|
||||||
controlConnection.shutdownTor("TERM");
|
logException(LOG, WARNING, e);
|
||||||
controlSocket.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,6 +403,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
TransportProperties remoteProperties = new TransportProperties();
|
TransportProperties remoteProperties = new TransportProperties();
|
||||||
remoteProperties.put(PROP_ONION_V3, remoteOnion);
|
remoteProperties.put(PROP_ONION_V3, remoteOnion);
|
||||||
try {
|
try {
|
||||||
|
@SuppressWarnings("resource")
|
||||||
ServerSocket ss = new ServerSocket();
|
ServerSocket ss = new ServerSocket();
|
||||||
ss.bind(new InetSocketAddress("127.0.0.1", 0));
|
ss.bind(new InetSocketAddress("127.0.0.1", 0));
|
||||||
int port = ss.getLocalPort();
|
int port = ss.getLocalPort();
|
||||||
@@ -717,9 +420,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
LOG.info("Rendezvous server socket closed");
|
LOG.info("Rendezvous server socket closed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Map<Integer, String> portLines =
|
tor.publishHiddenService(port, 80, blob);
|
||||||
singletonMap(80, "127.0.0.1:" + port);
|
|
||||||
controlConnection.addOnion(blob, portLines);
|
|
||||||
return new RendezvousEndpoint() {
|
return new RendezvousEndpoint() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -729,8 +430,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
controlConnection.delOnion(localOnion);
|
try {
|
||||||
tryToClose(ss, LOG, WARNING);
|
tor.removeHiddenService(localOnion);
|
||||||
|
} finally {
|
||||||
|
tryToClose(ss, LOG, WARNING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -739,121 +443,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void circuitStatus(String status, String id, String path) {
|
|
||||||
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
|
||||||
// DisableNetwork, set our circuitBuilt flag if not already set
|
|
||||||
if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
|
|
||||||
LOG.info("Circuit built");
|
|
||||||
backoff.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void streamStatus(String status, String id, String target) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void orConnStatus(String status, String orName) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
|
||||||
|
|
||||||
if (status.equals("CONNECTED")) state.onOrConnectionConnected();
|
|
||||||
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bandwidthUsed(long read, long written) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void newDescriptors(List<String> orList) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void message(String severity, String msg) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unrecognized(String type, String msg) {
|
|
||||||
if (type.equals("STATUS_CLIENT")) {
|
|
||||||
handleClientStatus(removeSeverity(msg));
|
|
||||||
} else if (type.equals("STATUS_GENERAL")) {
|
|
||||||
handleGeneralStatus(removeSeverity(msg));
|
|
||||||
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
|
||||||
String[] parts = msg.split(" ");
|
|
||||||
if (parts.length < 2) {
|
|
||||||
LOG.warning("Failed to parse HS_DESC UPLOADED event");
|
|
||||||
} else if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void controlConnectionClosed() {
|
|
||||||
if (state.isTorRunning()) {
|
|
||||||
// TODO: Restart the Tor process
|
|
||||||
LOG.warning("Control connection closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String removeSeverity(String msg) {
|
|
||||||
return msg.replaceFirst("[^ ]+ ", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleClientStatus(String msg) {
|
|
||||||
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
|
|
||||||
LOG.info("Bootstrapped");
|
|
||||||
state.setBootstrapped();
|
|
||||||
backoff.reset();
|
|
||||||
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
|
|
||||||
if (state.setCircuitBuilt(true)) {
|
|
||||||
LOG.info("Circuit built");
|
|
||||||
backoff.reset();
|
|
||||||
}
|
|
||||||
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
|
||||||
if (state.setCircuitBuilt(false)) {
|
|
||||||
LOG.info("Circuit not built");
|
|
||||||
// TODO: Disable and re-enable network to prompt Tor to rebuild
|
|
||||||
// its guard/bridge connections? This will also close any
|
|
||||||
// established circuits, which might still be functioning
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGeneralStatus(String msg) {
|
|
||||||
if (msg.startsWith("CLOCK_JUMPED")) {
|
|
||||||
Long time = parseLongArgument(msg, "TIME");
|
|
||||||
if (time != null && LOG.isLoggable(WARNING)) {
|
|
||||||
LOG.warning("Clock jumped " + time + " seconds");
|
|
||||||
}
|
|
||||||
} else if (msg.startsWith("CLOCK_SKEW")) {
|
|
||||||
Long skew = parseLongArgument(msg, "SKEW");
|
|
||||||
if (skew != null && LOG.isLoggable(WARNING)) {
|
|
||||||
LOG.warning("Clock is skewed by " + skew + " seconds");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Long parseLongArgument(String msg, String argName) {
|
|
||||||
String[] args = msg.split(" ");
|
|
||||||
for (String arg : args) {
|
|
||||||
if (arg.startsWith(argName + "=")) {
|
|
||||||
try {
|
|
||||||
return Long.parseLong(arg.substring(argName.length() + 1));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(WARNING)) {
|
|
||||||
LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof SettingsUpdatedEvent) {
|
if (e instanceof SettingsUpdatedEvent) {
|
||||||
@@ -876,7 +465,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private void updateConnectionStatus(NetworkStatus status,
|
private void updateConnectionStatus(NetworkStatus status,
|
||||||
boolean charging) {
|
boolean charging) {
|
||||||
connectionStatusExecutor.execute(() -> {
|
connectionStatusExecutor.execute(() -> {
|
||||||
if (!state.isTorRunning()) return;
|
if (!tor.isTorRunning()) return;
|
||||||
boolean online = status.isConnected();
|
boolean online = status.isConnected();
|
||||||
boolean wifi = status.isWifi();
|
boolean wifi = status.isWifi();
|
||||||
boolean ipv6Only = status.isIpv6Only();
|
boolean ipv6Only = status.isIpv6Only();
|
||||||
@@ -960,41 +549,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
try {
|
try {
|
||||||
if (enableNetwork) {
|
if (enableNetwork) {
|
||||||
enableBridges(bridgeTypes, country);
|
enableBridges(bridgeTypes, country);
|
||||||
enableConnectionPadding(enableConnectionPadding);
|
tor.enableConnectionPadding(enableConnectionPadding);
|
||||||
enableIpv6(ipv6Only);
|
tor.enableIpv6(ipv6Only);
|
||||||
}
|
}
|
||||||
enableNetwork(enableNetwork);
|
tor.enableNetwork(enableNetwork);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableConnectionPadding(boolean enable) throws IOException {
|
|
||||||
if (!state.enableConnectionPadding(enable)) return; // Unchanged
|
|
||||||
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableIpv6(boolean enable) throws IOException {
|
|
||||||
if (!state.enableIpv6(enable)) return; // Unchanged
|
|
||||||
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
|
|
||||||
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
protected class PluginState {
|
private class PluginState {
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private boolean started = false,
|
private boolean settingsChecked = false;
|
||||||
stopped = false,
|
|
||||||
networkInitialised = false,
|
|
||||||
networkEnabled = false,
|
|
||||||
paddingEnabled = false,
|
|
||||||
ipv6Enabled = false,
|
|
||||||
bootstrapped = false,
|
|
||||||
circuitBuilt = false,
|
|
||||||
settingsChecked = false;
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private int reasonsDisabled = 0;
|
private int reasonsDisabled = 0;
|
||||||
@@ -1003,84 +573,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private ServerSocket serverSocket = null;
|
private ServerSocket serverSocket = null;
|
||||||
|
|
||||||
@GuardedBy("this")
|
|
||||||
private int orConnectionsConnected = 0;
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
|
||||||
private List<BridgeType> bridgeTypes = emptyList();
|
|
||||||
|
|
||||||
private synchronized void setStarted() {
|
|
||||||
started = true;
|
|
||||||
callback.pluginStateChanged(getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
|
||||||
private synchronized boolean isTorRunning() {
|
|
||||||
return started && !stopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private synchronized ServerSocket setStopped() {
|
private synchronized ServerSocket setStopped() {
|
||||||
stopped = true;
|
|
||||||
ServerSocket ss = serverSocket;
|
ServerSocket ss = serverSocket;
|
||||||
serverSocket = null;
|
serverSocket = null;
|
||||||
callback.pluginStateChanged(getState());
|
|
||||||
return ss;
|
return ss;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setBootstrapped() {
|
|
||||||
boolean wasBootstrapped = bootstrapped;
|
|
||||||
bootstrapped = true;
|
|
||||||
if (!wasBootstrapped) callback.pluginStateChanged(getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the `circuitBuilt` flag and returns true if the flag has
|
|
||||||
* changed.
|
|
||||||
*/
|
|
||||||
private synchronized boolean setCircuitBuilt(boolean built) {
|
|
||||||
if (built == circuitBuilt) return false; // Unchanged
|
|
||||||
circuitBuilt = built;
|
|
||||||
callback.pluginStateChanged(getState());
|
|
||||||
return true; // Changed
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the `networkEnabled` flag and returns true if the flag has
|
|
||||||
* changed.
|
|
||||||
*/
|
|
||||||
private synchronized boolean enableNetwork(boolean enable) {
|
|
||||||
boolean wasInitialised = networkInitialised;
|
|
||||||
boolean wasEnabled = networkEnabled;
|
|
||||||
networkInitialised = true;
|
|
||||||
networkEnabled = enable;
|
|
||||||
if (!enable) circuitBuilt = false;
|
|
||||||
if (!wasInitialised || enable != wasEnabled) {
|
|
||||||
callback.pluginStateChanged(getState());
|
|
||||||
}
|
|
||||||
return enable != wasEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the `paddingEnabled` flag and returns true if the flag has
|
|
||||||
* changed. Doesn't affect getState().
|
|
||||||
*/
|
|
||||||
private synchronized boolean enableConnectionPadding(boolean enable) {
|
|
||||||
if (enable == paddingEnabled) return false; // Unchanged
|
|
||||||
paddingEnabled = enable;
|
|
||||||
return true; // Changed
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the `ipv6Enabled` flag and returns true if the flag has
|
|
||||||
* changed. Doesn't affect getState().
|
|
||||||
*/
|
|
||||||
private synchronized boolean enableIpv6(boolean enable) {
|
|
||||||
if (enable == ipv6Enabled) return false; // Unchanged
|
|
||||||
ipv6Enabled = enable;
|
|
||||||
return true; // Changed
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void setReasonsDisabled(int reasons) {
|
private synchronized void setReasonsDisabled(int reasons) {
|
||||||
boolean wasChecked = settingsChecked;
|
boolean wasChecked = settingsChecked;
|
||||||
settingsChecked = true;
|
settingsChecked = true;
|
||||||
@@ -1093,7 +592,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
// Doesn't affect getState()
|
// Doesn't affect getState()
|
||||||
private synchronized boolean setServerSocket(ServerSocket ss) {
|
private synchronized boolean setServerSocket(ServerSocket ss) {
|
||||||
if (stopped || serverSocket != null) return false;
|
if (serverSocket != null || !tor.isTorRunning()) return false;
|
||||||
serverSocket = ss;
|
serverSocket = ss;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1103,57 +602,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (serverSocket == ss) serverSocket = null;
|
if (serverSocket == ss) serverSocket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private synchronized State getState() {
|
||||||
* Sets the list of bridge types being used and returns true if the
|
return getState(tor.getTorState());
|
||||||
* list has changed. The list is empty if bridges are disabled.
|
|
||||||
* Doesn't affect getState().
|
|
||||||
*/
|
|
||||||
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
|
|
||||||
if (types.equals(bridgeTypes)) return false; // Unchanged
|
|
||||||
bridgeTypes = types;
|
|
||||||
return true; // Changed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized State getState() {
|
private synchronized State getState(TorState torState) {
|
||||||
if (!started || stopped || !settingsChecked) {
|
if (torState == TorState.STARTING_STOPPING || !settingsChecked) {
|
||||||
return STARTING_STOPPING;
|
return STARTING_STOPPING;
|
||||||
}
|
}
|
||||||
if (reasonsDisabled != 0) return DISABLED;
|
if (reasonsDisabled != 0) return DISABLED;
|
||||||
if (!networkInitialised) return ENABLING;
|
if (torState == TorState.CONNECTING) return ENABLING;
|
||||||
if (!networkEnabled) return INACTIVE;
|
if (torState == TorState.CONNECTED) return ACTIVE;
|
||||||
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
|
return INACTIVE;
|
||||||
? ACTIVE : ENABLING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized int getReasonsDisabled() {
|
private synchronized int getReasonsDisabled() {
|
||||||
return getState() == DISABLED ? reasonsDisabled : 0;
|
return getState() == DISABLED ? reasonsDisabled : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onOrConnectionConnected() {
|
|
||||||
int oldConnected = orConnectionsConnected;
|
|
||||||
orConnectionsConnected++;
|
|
||||||
logOrConnections();
|
|
||||||
if (oldConnected == 0) callback.pluginStateChanged(getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void onOrConnectionClosed() {
|
|
||||||
int oldConnected = orConnectionsConnected;
|
|
||||||
orConnectionsConnected--;
|
|
||||||
if (orConnectionsConnected < 0) {
|
|
||||||
LOG.warning("Count was zero before connection closed");
|
|
||||||
orConnectionsConnected = 0;
|
|
||||||
}
|
|
||||||
logOrConnections();
|
|
||||||
if (orConnectionsConnected == 0 && oldConnected != 0) {
|
|
||||||
callback.pluginStateChanged(getState());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
|
||||||
private void logOrConnections() {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(orConnectionsConnected + " OR connections connected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
|||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
@@ -17,7 +18,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
@@ -45,13 +45,12 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
|
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
|
||||||
private static final double BACKOFF_BASE = 1.2;
|
private static final double BACKOFF_BASE = 1.2;
|
||||||
|
|
||||||
protected final Executor ioExecutor, wakefulIoExecutor;
|
protected final Executor ioExecutor, eventExecutor, wakefulIoExecutor;
|
||||||
protected final NetworkManager networkManager;
|
protected final NetworkManager networkManager;
|
||||||
protected final LocationUtils locationUtils;
|
protected final LocationUtils locationUtils;
|
||||||
protected final EventBus eventBus;
|
protected final EventBus eventBus;
|
||||||
protected final SocketFactory torSocketFactory;
|
protected final SocketFactory torSocketFactory;
|
||||||
protected final BackoffFactory backoffFactory;
|
protected final BackoffFactory backoffFactory;
|
||||||
protected final ResourceProvider resourceProvider;
|
|
||||||
protected final CircumventionProvider circumventionProvider;
|
protected final CircumventionProvider circumventionProvider;
|
||||||
protected final BatteryManager batteryManager;
|
protected final BatteryManager batteryManager;
|
||||||
protected final Clock clock;
|
protected final Clock clock;
|
||||||
@@ -61,13 +60,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
protected final int torControlPort;
|
protected final int torControlPort;
|
||||||
|
|
||||||
TorPluginFactory(@IoExecutor Executor ioExecutor,
|
TorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
|
@EventExecutor Executor eventExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
SocketFactory torSocketFactory,
|
SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory,
|
BackoffFactory backoffFactory,
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
@@ -76,13 +75,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
@TorSocksPort int torSocksPort,
|
@TorSocksPort int torSocksPort,
|
||||||
@TorControlPort int torControlPort) {
|
@TorControlPort int torControlPort) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.eventExecutor = eventExecutor;
|
||||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||||
this.networkManager = networkManager;
|
this.networkManager = networkManager;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.torSocketFactory = torSocketFactory;
|
this.torSocketFactory = torSocketFactory;
|
||||||
this.backoffFactory = backoffFactory;
|
this.backoffFactory = backoffFactory;
|
||||||
this.resourceProvider = resourceProvider;
|
|
||||||
this.circumventionProvider = circumventionProvider;
|
this.circumventionProvider = circumventionProvider;
|
||||||
this.batteryManager = batteryManager;
|
this.batteryManager = batteryManager;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
|
|||||||
@@ -0,0 +1,673 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor.wrapper;
|
||||||
|
|
||||||
|
import net.freehaven.tor.control.EventHandler;
|
||||||
|
import net.freehaven.tor.control.TorControlConnection;
|
||||||
|
|
||||||
|
import org.briarproject.nullsafety.InterfaceNotNullByDefault;
|
||||||
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||||
|
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.UTF_8;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.copyAndClose;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.scrubOnion;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.tryToClose;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.CONNECTED;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.CONNECTING;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.DISABLED;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.STARTING_STOPPING;
|
||||||
|
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
||||||
|
|
||||||
|
@InterfaceNotNullByDefault
|
||||||
|
abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
|
||||||
|
|
||||||
|
private static final String[] EVENTS = {
|
||||||
|
"CIRC",
|
||||||
|
"ORCONN",
|
||||||
|
"STATUS_GENERAL",
|
||||||
|
"STATUS_CLIENT",
|
||||||
|
"HS_DESC",
|
||||||
|
"NOTICE",
|
||||||
|
"WARN",
|
||||||
|
"ERR"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String OWNER = "__OwningControllerProcess";
|
||||||
|
private static final int COOKIE_TIMEOUT_MS = 3000;
|
||||||
|
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
||||||
|
|
||||||
|
protected final Executor ioExecutor;
|
||||||
|
protected final Executor eventExecutor;
|
||||||
|
private final String architecture;
|
||||||
|
private final File torDirectory, configFile, doneFile, cookieFile;
|
||||||
|
private final int torSocksPort;
|
||||||
|
private final int torControlPort;
|
||||||
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
protected final NetworkState state = new NetworkState();
|
||||||
|
|
||||||
|
private volatile Socket controlSocket = null;
|
||||||
|
private volatile TorControlConnection controlConnection = null;
|
||||||
|
|
||||||
|
protected abstract int getProcessId();
|
||||||
|
|
||||||
|
protected abstract long getLastUpdateTime();
|
||||||
|
|
||||||
|
protected abstract InputStream getResourceInputStream(String name,
|
||||||
|
String extension);
|
||||||
|
|
||||||
|
AbstractTorWrapper(Executor ioExecutor,
|
||||||
|
Executor eventExecutor,
|
||||||
|
String architecture,
|
||||||
|
File torDirectory,
|
||||||
|
int torSocksPort,
|
||||||
|
int torControlPort) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.eventExecutor = eventExecutor;
|
||||||
|
this.architecture = architecture;
|
||||||
|
this.torDirectory = torDirectory;
|
||||||
|
this.torSocksPort = torSocksPort;
|
||||||
|
this.torControlPort = torControlPort;
|
||||||
|
configFile = new File(torDirectory, "torrc");
|
||||||
|
doneFile = new File(torDirectory, "done");
|
||||||
|
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected File getTorExecutableFile() {
|
||||||
|
return new File(torDirectory, "tor");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected File getObfs4ExecutableFile() {
|
||||||
|
return new File(torDirectory, "obfs4proxy");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected File getSnowflakeExecutableFile() {
|
||||||
|
return new File(torDirectory, "snowflake");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStateObserver(@Nullable StateObserver stateObserver) {
|
||||||
|
state.setObserver(stateObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() throws IOException, InterruptedException {
|
||||||
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
|
if (!torDirectory.exists()) {
|
||||||
|
if (!torDirectory.mkdirs()) {
|
||||||
|
throw new IOException("Could not create Tor directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Install or update the assets if necessary
|
||||||
|
if (!assetsAreUpToDate()) installAssets();
|
||||||
|
// Start from the default config every time
|
||||||
|
extract(getConfigInputStream(), configFile);
|
||||||
|
if (cookieFile.exists() && !cookieFile.delete())
|
||||||
|
LOG.warning("Old auth cookie not deleted");
|
||||||
|
// Start a new Tor process
|
||||||
|
LOG.info("Starting Tor");
|
||||||
|
File torFile = getTorExecutableFile();
|
||||||
|
String torPath = torFile.getAbsolutePath();
|
||||||
|
String configPath = configFile.getAbsolutePath();
|
||||||
|
String pid = String.valueOf(getProcessId());
|
||||||
|
Process torProcess;
|
||||||
|
ProcessBuilder pb =
|
||||||
|
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
|
||||||
|
Map<String, String> env = pb.environment();
|
||||||
|
env.put("HOME", torDirectory.getAbsolutePath());
|
||||||
|
pb.directory(torDirectory);
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
try {
|
||||||
|
torProcess = pb.start();
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
// Wait for the Tor process to start
|
||||||
|
waitForTorToStart(torProcess);
|
||||||
|
// Wait for the auth cookie file to be created/updated
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (cookieFile.length() < 32) {
|
||||||
|
if (System.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
|
||||||
|
throw new IOException("Auth cookie not created");
|
||||||
|
}
|
||||||
|
//noinspection BusyWait
|
||||||
|
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
LOG.info("Auth cookie created");
|
||||||
|
// Open a control connection and authenticate using the cookie file
|
||||||
|
controlSocket = new Socket("127.0.0.1", torControlPort);
|
||||||
|
controlConnection = new TorControlConnection(controlSocket);
|
||||||
|
controlConnection.authenticate(read(cookieFile));
|
||||||
|
// Tell Tor to exit when the control connection is closed
|
||||||
|
controlConnection.takeOwnership();
|
||||||
|
controlConnection.resetConf(singletonList(OWNER));
|
||||||
|
// Register to receive events from the Tor process
|
||||||
|
controlConnection.setEventHandler(this);
|
||||||
|
controlConnection.setEvents(asList(EVENTS));
|
||||||
|
// Check whether Tor has already bootstrapped
|
||||||
|
String info = controlConnection.getInfo("status/bootstrap-phase");
|
||||||
|
if (info != null && info.contains("PROGRESS=100")) {
|
||||||
|
LOG.info("Tor has already bootstrapped");
|
||||||
|
state.setBootstrapped();
|
||||||
|
}
|
||||||
|
// Check whether Tor has already built a circuit
|
||||||
|
info = controlConnection.getInfo("status/circuit-established");
|
||||||
|
if ("1".equals(info)) {
|
||||||
|
LOG.info("Tor has already built a circuit");
|
||||||
|
state.setCircuitBuilt(true);
|
||||||
|
}
|
||||||
|
state.setStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean assetsAreUpToDate() {
|
||||||
|
return doneFile.lastModified() > getLastUpdateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void installAssets() throws IOException {
|
||||||
|
// The done file may already exist from a previous installation
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
doneFile.delete();
|
||||||
|
installTorExecutable();
|
||||||
|
installObfs4Executable();
|
||||||
|
installSnowflakeExecutable();
|
||||||
|
extract(getConfigInputStream(), configFile);
|
||||||
|
if (!doneFile.createNewFile()) {
|
||||||
|
LOG.warning("Failed to create done file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void extract(InputStream in, File dest) throws IOException {
|
||||||
|
@SuppressWarnings("IOStreamConstructor")
|
||||||
|
OutputStream out = new FileOutputStream(dest);
|
||||||
|
copyAndClose(in, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void installTorExecutable() throws IOException {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Installing Tor binary for " + architecture);
|
||||||
|
}
|
||||||
|
File torFile = getTorExecutableFile();
|
||||||
|
extract(getExecutableInputStream("tor"), torFile);
|
||||||
|
if (!torFile.setExecutable(true, true)) throw new IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void installObfs4Executable() throws IOException {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Installing obfs4proxy binary for " + architecture);
|
||||||
|
}
|
||||||
|
File obfs4File = getObfs4ExecutableFile();
|
||||||
|
extract(getExecutableInputStream("obfs4proxy"), obfs4File);
|
||||||
|
if (!obfs4File.setExecutable(true, true)) throw new IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void installSnowflakeExecutable() throws IOException {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Installing snowflake binary for " + architecture);
|
||||||
|
}
|
||||||
|
File snowflakeFile = getSnowflakeExecutableFile();
|
||||||
|
extract(getExecutableInputStream("snowflake"), snowflakeFile);
|
||||||
|
if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getExecutableInputStream(String basename) {
|
||||||
|
String ext = getExecutableExtension();
|
||||||
|
return requireNonNull(
|
||||||
|
getResourceInputStream(architecture + "/" + basename, ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getExecutableExtension() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void append(StringBuilder strb, String name, Object value) {
|
||||||
|
strb.append(name);
|
||||||
|
strb.append(" ");
|
||||||
|
strb.append(value);
|
||||||
|
strb.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getConfigInputStream() {
|
||||||
|
File dataDirectory = new File(torDirectory, ".tor");
|
||||||
|
StringBuilder strb = new StringBuilder();
|
||||||
|
append(strb, "ControlPort", torControlPort);
|
||||||
|
append(strb, "CookieAuthentication", 1);
|
||||||
|
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
|
||||||
|
append(strb, "DisableNetwork", 1);
|
||||||
|
append(strb, "RunAsDaemon", 1);
|
||||||
|
append(strb, "SafeSocks", 1);
|
||||||
|
append(strb, "SocksPort", torSocksPort);
|
||||||
|
strb.append("GeoIPFile\n");
|
||||||
|
strb.append("GeoIPv6File\n");
|
||||||
|
append(strb, "ConnectionPadding", 0);
|
||||||
|
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
|
||||||
|
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
|
||||||
|
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
|
||||||
|
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
|
||||||
|
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
|
||||||
|
return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] read(File f) throws IOException {
|
||||||
|
byte[] b = new byte[(int) f.length()];
|
||||||
|
FileInputStream in = new FileInputStream(f);
|
||||||
|
try {
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < b.length) {
|
||||||
|
int read = in.read(b, offset, b.length - offset);
|
||||||
|
if (read == -1) throw new EOFException();
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
} finally {
|
||||||
|
tryToClose(in, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitForTorToStart(Process torProcess)
|
||||||
|
throws InterruptedException, IOException {
|
||||||
|
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||||
|
// Log the first line of stdout (contains Tor and library versions)
|
||||||
|
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||||
|
// Read the process's stdout (and redirected stderr) until it detaches
|
||||||
|
while (stdout.hasNextLine()) stdout.nextLine();
|
||||||
|
stdout.close();
|
||||||
|
// Wait for the process to detach or exit
|
||||||
|
int exit = torProcess.waitFor();
|
||||||
|
if (exit != 0) throw new IOException("Tor exited with value " + exit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HiddenServiceProperties publishHiddenService(int localPort,
|
||||||
|
int remotePort, @Nullable String privKey) throws IOException {
|
||||||
|
Map<Integer, String> portLines =
|
||||||
|
singletonMap(remotePort, "127.0.0.1:" + localPort);
|
||||||
|
// Use the control connection to set up the hidden service
|
||||||
|
Map<String, String> response;
|
||||||
|
if (privKey == null) {
|
||||||
|
response = getControlConnection().addOnion("NEW:ED25519-V3",
|
||||||
|
portLines, null);
|
||||||
|
} else {
|
||||||
|
response = getControlConnection().addOnion(privKey, portLines);
|
||||||
|
}
|
||||||
|
if (!response.containsKey(HS_ADDRESS)) {
|
||||||
|
throw new IOException("Missing hidden service address");
|
||||||
|
}
|
||||||
|
if (privKey == null && !response.containsKey(HS_PRIVKEY)) {
|
||||||
|
throw new IOException("Missing private key");
|
||||||
|
}
|
||||||
|
String onion = response.get(HS_ADDRESS);
|
||||||
|
if (privKey == null) privKey = response.get(HS_PRIVKEY);
|
||||||
|
return new HiddenServiceProperties(onion, privKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeHiddenService(String onion) throws IOException {
|
||||||
|
getControlConnection().delOnion(onion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableNetwork(boolean enable) throws IOException {
|
||||||
|
if (!state.enableNetwork(enable)) return; // Unchanged
|
||||||
|
getControlConnection().setConf("DisableNetwork", enable ? "0" : "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableBridges(List<String> bridges) throws IOException {
|
||||||
|
if (!state.setBridges(bridges)) return; // Unchanged
|
||||||
|
List<String> conf = new ArrayList<>(bridges.size() + 1);
|
||||||
|
conf.add("UseBridges 1");
|
||||||
|
conf.addAll(bridges);
|
||||||
|
getControlConnection().setConf(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableBridges() throws IOException {
|
||||||
|
if (!state.setBridges(emptyList())) return; // Unchanged
|
||||||
|
getControlConnection().setConf("UseBridges", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() throws IOException {
|
||||||
|
state.setStopped();
|
||||||
|
if (controlSocket != null && controlConnection != null) {
|
||||||
|
LOG.info("Stopping Tor");
|
||||||
|
try {
|
||||||
|
controlConnection.shutdownTor("TERM");
|
||||||
|
} finally {
|
||||||
|
tryToClose(controlSocket, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void circuitStatus(String status, String id, String path) {
|
||||||
|
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
||||||
|
// DisableNetwork, set our circuitBuilt flag if not already set
|
||||||
|
if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
|
||||||
|
LOG.info("Circuit built");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void streamStatus(String status, String id, String target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void orConnStatus(String status, String orName) {
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||||
|
|
||||||
|
if (status.equals("CONNECTED")) state.onOrConnectionConnected();
|
||||||
|
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bandwidthUsed(long read, long written) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void newDescriptors(List<String> orList) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void message(String severity, String msg) {
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unrecognized(String type, String msg) {
|
||||||
|
if (type.equals("STATUS_CLIENT")) {
|
||||||
|
handleClientStatus(removeSeverity(msg));
|
||||||
|
} else if (type.equals("STATUS_GENERAL")) {
|
||||||
|
handleGeneralStatus(removeSeverity(msg));
|
||||||
|
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
||||||
|
String[] parts = msg.split(" ");
|
||||||
|
if (parts.length < 2) {
|
||||||
|
LOG.warning("Failed to parse HS_DESC UPLOADED event");
|
||||||
|
} else if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String removeSeverity(String msg) {
|
||||||
|
return msg.replaceFirst("[^ ]+ ", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleClientStatus(String msg) {
|
||||||
|
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
|
||||||
|
LOG.info("Bootstrapped");
|
||||||
|
state.setBootstrapped();
|
||||||
|
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
|
||||||
|
if (state.setCircuitBuilt(true)) LOG.info("Circuit built");
|
||||||
|
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
||||||
|
if (state.setCircuitBuilt(false)) {
|
||||||
|
LOG.info("Circuit not built");
|
||||||
|
// TODO: Disable and re-enable network to prompt Tor to rebuild
|
||||||
|
// its guard/bridge connections? This will also close any
|
||||||
|
// established circuits, which might still be functioning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGeneralStatus(String msg) {
|
||||||
|
if (msg.startsWith("CLOCK_JUMPED")) {
|
||||||
|
Long time = parseLongArgument(msg, "TIME");
|
||||||
|
if (time != null && LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("Clock jumped " + time + " seconds");
|
||||||
|
}
|
||||||
|
} else if (msg.startsWith("CLOCK_SKEW")) {
|
||||||
|
Long skew = parseLongArgument(msg, "SKEW");
|
||||||
|
if (skew != null && LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("Clock is skewed by " + skew + " seconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Long parseLongArgument(String msg, String argName) {
|
||||||
|
String[] args = msg.split(" ");
|
||||||
|
for (String arg : args) {
|
||||||
|
if (arg.startsWith(argName + "=")) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(arg.substring(argName.length() + 1));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void controlConnectionClosed() {
|
||||||
|
if (state.isTorRunning()) {
|
||||||
|
// TODO: Restart the Tor process
|
||||||
|
LOG.warning("Control connection closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableConnectionPadding(boolean enable) throws IOException {
|
||||||
|
if (!state.enableConnectionPadding(enable)) return; // Unchanged
|
||||||
|
getControlConnection().setConf("ConnectionPadding", enable ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableIpv6(boolean enable) throws IOException {
|
||||||
|
if (!state.enableIpv6(enable)) return; // Unchanged
|
||||||
|
getControlConnection().setConf("ClientUseIPv4", enable ? "0" : "1");
|
||||||
|
getControlConnection().setConf("ClientUseIPv6", enable ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TorState getTorState() {
|
||||||
|
return state.getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTorRunning() {
|
||||||
|
return state.isTorRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TorControlConnection getControlConnection() throws IOException {
|
||||||
|
TorControlConnection controlConnection = this.controlConnection;
|
||||||
|
if (controlConnection == null) {
|
||||||
|
throw new IOException("Control connection not opened");
|
||||||
|
}
|
||||||
|
return controlConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
private class NetworkState {
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
@Nullable
|
||||||
|
private StateObserver observer = null;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private boolean started = false,
|
||||||
|
stopped = false,
|
||||||
|
networkInitialised = false,
|
||||||
|
networkEnabled = false,
|
||||||
|
paddingEnabled = false,
|
||||||
|
ipv6Enabled = false,
|
||||||
|
bootstrapped = false,
|
||||||
|
circuitBuilt = false;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private int orConnectionsConnected = 0;
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
@Nullable
|
||||||
|
private TorState state = null;
|
||||||
|
|
||||||
|
private synchronized void setObserver(
|
||||||
|
@Nullable StateObserver observer) {
|
||||||
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private void updateObserver() {
|
||||||
|
TorState newState = getState();
|
||||||
|
if (newState != state) {
|
||||||
|
state = newState;
|
||||||
|
// Post the new state to the event executor. The contract of
|
||||||
|
// this executor is to execute tasks in the order they're
|
||||||
|
// submitted, so state changes will be observed in the correct
|
||||||
|
// order but outside the lock
|
||||||
|
if (observer != null) {
|
||||||
|
eventExecutor.execute(() ->
|
||||||
|
observer.observeState(newState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private List<String> bridges = emptyList();
|
||||||
|
|
||||||
|
private synchronized void setStarted() {
|
||||||
|
started = true;
|
||||||
|
updateObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private synchronized boolean isTorRunning() {
|
||||||
|
return started && !stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void setStopped() {
|
||||||
|
stopped = true;
|
||||||
|
updateObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void setBootstrapped() {
|
||||||
|
if (bootstrapped) return;
|
||||||
|
bootstrapped = true;
|
||||||
|
updateObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `circuitBuilt` flag and returns true if the flag has
|
||||||
|
* changed.
|
||||||
|
*/
|
||||||
|
private synchronized boolean setCircuitBuilt(boolean built) {
|
||||||
|
if (built == circuitBuilt) return false; // Unchanged
|
||||||
|
circuitBuilt = built;
|
||||||
|
updateObserver();
|
||||||
|
return true; // Changed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `networkEnabled` flag and returns true if the flag has
|
||||||
|
* changed.
|
||||||
|
*/
|
||||||
|
private synchronized boolean enableNetwork(boolean enable) {
|
||||||
|
boolean wasInitialised = networkInitialised;
|
||||||
|
boolean wasEnabled = networkEnabled;
|
||||||
|
networkInitialised = true;
|
||||||
|
networkEnabled = enable;
|
||||||
|
if (!enable) circuitBuilt = false;
|
||||||
|
if (!wasInitialised || enable != wasEnabled) {
|
||||||
|
updateObserver();
|
||||||
|
}
|
||||||
|
return enable != wasEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `paddingEnabled` flag and returns true if the flag has
|
||||||
|
* changed. Doesn't affect getState().
|
||||||
|
*/
|
||||||
|
private synchronized boolean enableConnectionPadding(boolean enable) {
|
||||||
|
if (enable == paddingEnabled) return false; // Unchanged
|
||||||
|
paddingEnabled = enable;
|
||||||
|
return true; // Changed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `ipv6Enabled` flag and returns true if the flag has
|
||||||
|
* changed. Doesn't affect getState().
|
||||||
|
*/
|
||||||
|
private synchronized boolean enableIpv6(boolean enable) {
|
||||||
|
if (enable == ipv6Enabled) return false; // Unchanged
|
||||||
|
ipv6Enabled = enable;
|
||||||
|
return true; // Changed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of bridges being used and returns true if the
|
||||||
|
* list has changed. The list is empty if bridges are disabled.
|
||||||
|
* Doesn't affect getState().
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private synchronized boolean setBridges(List<String> bridges) {
|
||||||
|
if (this.bridges.equals(bridges)) return false; // Unchanged
|
||||||
|
this.bridges = bridges;
|
||||||
|
return true; // Changed
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized TorState getState() {
|
||||||
|
if (!started || stopped) return STARTING_STOPPING;
|
||||||
|
if (!networkInitialised) return CONNECTING;
|
||||||
|
if (!networkEnabled) return DISABLED;
|
||||||
|
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
|
||||||
|
? CONNECTED : CONNECTING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onOrConnectionConnected() {
|
||||||
|
int oldConnected = orConnectionsConnected;
|
||||||
|
orConnectionsConnected++;
|
||||||
|
logOrConnections();
|
||||||
|
if (oldConnected == 0) updateObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onOrConnectionClosed() {
|
||||||
|
int oldConnected = orConnectionsConnected;
|
||||||
|
orConnectionsConnected--;
|
||||||
|
if (orConnectionsConnected < 0) {
|
||||||
|
LOG.warning("Count was zero before connection closed");
|
||||||
|
orConnectionsConnected = 0;
|
||||||
|
}
|
||||||
|
logOrConnections();
|
||||||
|
if (orConnectionsConnected == 0 && oldConnected != 0) {
|
||||||
|
updateObserver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private void logOrConnections() {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(orConnectionsConnected + " OR connections connected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor.wrapper;
|
||||||
|
|
||||||
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.LOG;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class TorUtils {
|
||||||
|
|
||||||
|
@SuppressWarnings("CharsetObjectCanBeUsed")
|
||||||
|
static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
static String scrubOnion(String onion) {
|
||||||
|
// Keep first three characters of onion address
|
||||||
|
return onion.substring(0, 3) + "[scrubbed]";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copyAndClose(InputStream in, OutputStream out) {
|
||||||
|
byte[] buf = new byte[4096];
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
int read = in.read(buf);
|
||||||
|
if (read == -1) break;
|
||||||
|
out.write(buf, 0, read);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
tryToClose(in, LOG, WARNING);
|
||||||
|
tryToClose(out, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tryToClose(@Nullable Closeable c, Logger logger, Level level) {
|
||||||
|
try {
|
||||||
|
if (c != null) c.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(logger, level, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tryToClose(@Nullable Socket s, Logger logger, Level level) {
|
||||||
|
try {
|
||||||
|
if (s != null) s.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(logger, level, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logException(Logger logger, Level level, Throwable t) {
|
||||||
|
if (logger.isLoggable(level)) logger.log(level, t.toString(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor.wrapper;
|
||||||
|
|
||||||
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface TorWrapper {
|
||||||
|
|
||||||
|
Logger LOG = getLogger(TorWrapper.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the Tor process.
|
||||||
|
* <p>
|
||||||
|
* This method must only be called once. To restart the Tor process, stop
|
||||||
|
* this wrapper instance and then create a new instance.
|
||||||
|
*/
|
||||||
|
void start() throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the Tor process to stop and returns without waiting for the
|
||||||
|
* process to exit.
|
||||||
|
*/
|
||||||
|
void stop() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an observer for observing the state of the wrapper, replacing any
|
||||||
|
* existing observer, or removes any existing observer if the argument is
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
void setStateObserver(@Nullable StateObserver stateObserver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of the wrapper.
|
||||||
|
*/
|
||||||
|
TorState getTorState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the wrapper has been {@link #start() started} and not
|
||||||
|
* yet {@link #stop()} stopped.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
boolean isTorRunning();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes an ephemeral hidden service.
|
||||||
|
*
|
||||||
|
* @param localPort The local port on which the service is listening.
|
||||||
|
* @param remotePort The port number that clients of the service will see.
|
||||||
|
* @param privateKey The private key of the hidden service, in the form
|
||||||
|
* returned by a previous call to this method, or null if a new service
|
||||||
|
* should be created.
|
||||||
|
*/
|
||||||
|
HiddenServiceProperties publishHiddenService(int localPort,
|
||||||
|
int remotePort, @Nullable String privateKey) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes (unpublishes) an ephemeral hidden service that was created by
|
||||||
|
* calling {@link #publishHiddenService(int, int, String)}.
|
||||||
|
*/
|
||||||
|
void removeHiddenService(String onion) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the Tor process's network connection. The network
|
||||||
|
* connection is disabled by default.
|
||||||
|
*/
|
||||||
|
void enableNetwork(boolean enable) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures Tor to use the given list of bridges for connecting to the
|
||||||
|
* Tor network. Bridges are not used by default.
|
||||||
|
* <p>
|
||||||
|
* Each item in the list should be a bridge line in the same
|
||||||
|
* format that would be used in a torrc file (including the Bridge keyword).
|
||||||
|
*/
|
||||||
|
void enableBridges(List<String> bridges) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures Tor not to use bridges for connecting to the Tor network.
|
||||||
|
* Bridges are not used by default.
|
||||||
|
*/
|
||||||
|
void disableBridges() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables connection padding. Padding is disabled by default.
|
||||||
|
*/
|
||||||
|
void enableConnectionPadding(boolean enable) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures Tor to use IPv6 or IPv4 for connecting to the Tor network.
|
||||||
|
* IPv4 is used by default.
|
||||||
|
*/
|
||||||
|
void enableIpv6(boolean ipv6Only) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the Tor wrapper.
|
||||||
|
*/
|
||||||
|
enum TorState {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tor process is either starting or stopping.
|
||||||
|
*/
|
||||||
|
STARTING_STOPPING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tor process has started, its network connection is enabled, and
|
||||||
|
* it is connecting (or reconnecting) to the Tor network.
|
||||||
|
*/
|
||||||
|
CONNECTING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tor process has started, its network connection is enabled, and
|
||||||
|
* it has connected to the Tor network. In this state it should be
|
||||||
|
* possible to make connections via the SOCKS port.
|
||||||
|
*/
|
||||||
|
CONNECTED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tor process has started but its network connection is disabled.
|
||||||
|
*/
|
||||||
|
DISABLED
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for observing changes to the {@link TorState state} of the
|
||||||
|
* Tor process.
|
||||||
|
*/
|
||||||
|
interface StateObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called whenever the state of the Tor process changes.
|
||||||
|
* The call happens on the event executor supplied to the wrapper's
|
||||||
|
* constructor.
|
||||||
|
*/
|
||||||
|
void observeState(TorState s);
|
||||||
|
}
|
||||||
|
|
||||||
|
class HiddenServiceProperties {
|
||||||
|
|
||||||
|
public final String onion, privKey;
|
||||||
|
|
||||||
|
HiddenServiceProperties(String onion, String privKey) {
|
||||||
|
this.onion = onion;
|
||||||
|
this.privKey = privKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.CodeSource;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
abstract class JavaTorPlugin extends TorPlugin {
|
|
||||||
|
|
||||||
JavaTorPlugin(Executor ioExecutor,
|
|
||||||
Executor wakefulIoExecutor,
|
|
||||||
NetworkManager networkManager,
|
|
||||||
LocationUtils locationUtils,
|
|
||||||
SocketFactory torSocketFactory,
|
|
||||||
Clock clock,
|
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
|
||||||
BatteryManager batteryManager,
|
|
||||||
Backoff backoff,
|
|
||||||
TorRendezvousCrypto torRendezvousCrypto,
|
|
||||||
PluginCallback callback,
|
|
||||||
String architecture,
|
|
||||||
long maxLatency,
|
|
||||||
int maxIdleTime,
|
|
||||||
File torDirectory,
|
|
||||||
int torSocksPort,
|
|
||||||
int torControlPort) {
|
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
|
||||||
torSocketFactory, clock, resourceProvider,
|
|
||||||
circumventionProvider, batteryManager, backoff,
|
|
||||||
torRendezvousCrypto, callback, architecture,
|
|
||||||
maxLatency, maxIdleTime, torDirectory, torSocksPort,
|
|
||||||
torControlPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long getLastUpdateTime() {
|
|
||||||
CodeSource codeSource =
|
|
||||||
getClass().getProtectionDomain().getCodeSource();
|
|
||||||
if (codeSource == null) throw new AssertionError("CodeSource null");
|
|
||||||
try {
|
|
||||||
URI path = codeSource.getLocation().toURI();
|
|
||||||
File file = new File(path);
|
|
||||||
return file.lastModified();
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
|
||||||
|
|
||||||
import com.sun.jna.Library;
|
|
||||||
import com.sun.jna.Native;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class UnixTorPlugin extends JavaTorPlugin {
|
|
||||||
|
|
||||||
UnixTorPlugin(Executor ioExecutor,
|
|
||||||
Executor wakefulIoExecutor,
|
|
||||||
NetworkManager networkManager,
|
|
||||||
LocationUtils locationUtils,
|
|
||||||
SocketFactory torSocketFactory,
|
|
||||||
Clock clock,
|
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
|
||||||
BatteryManager batteryManager,
|
|
||||||
Backoff backoff,
|
|
||||||
TorRendezvousCrypto torRendezvousCrypto,
|
|
||||||
PluginCallback callback,
|
|
||||||
String architecture,
|
|
||||||
long maxLatency,
|
|
||||||
int maxIdleTime,
|
|
||||||
File torDirectory,
|
|
||||||
int torSocksPort,
|
|
||||||
int torControlPort) {
|
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
|
||||||
torSocketFactory, clock, resourceProvider,
|
|
||||||
circumventionProvider, batteryManager, backoff,
|
|
||||||
torRendezvousCrypto, callback, architecture,
|
|
||||||
maxLatency, maxIdleTime, torDirectory, torSocksPort,
|
|
||||||
torControlPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getProcessId() {
|
|
||||||
return CLibrary.INSTANCE.getpid();
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface CLibrary extends Library {
|
|
||||||
|
|
||||||
CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class);
|
|
||||||
|
|
||||||
int getpid();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
|||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
@@ -13,8 +14,9 @@ import org.briarproject.bramble.api.plugin.TorDirectory;
|
|||||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.UnixTorWrapper;
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -34,13 +36,13 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
|
@EventExecutor Executor eventExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
SocketFactory torSocketFactory,
|
SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory,
|
BackoffFactory backoffFactory,
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
@@ -48,8 +50,8 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
|||||||
@TorDirectory File torDirectory,
|
@TorDirectory File torDirectory,
|
||||||
@TorSocksPort int torSocksPort,
|
@TorSocksPort int torSocksPort,
|
||||||
@TorControlPort int torControlPort) {
|
@TorControlPort int torControlPort) {
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||||
circumventionProvider, batteryManager, clock, crypto,
|
circumventionProvider, batteryManager, clock, crypto,
|
||||||
torDirectory, torSocksPort, torControlPort);
|
torDirectory, torSocksPort, torControlPort);
|
||||||
}
|
}
|
||||||
@@ -62,6 +64,7 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
|||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("System's os.arch is " + arch);
|
LOG.info("System's os.arch is " + arch);
|
||||||
}
|
}
|
||||||
|
//noinspection IfCanBeSwitch
|
||||||
if (arch.equals("amd64")) return "x86_64";
|
if (arch.equals("amd64")) return "x86_64";
|
||||||
else if (arch.equals("aarch64")) return "aarch64";
|
else if (arch.equals("aarch64")) return "aarch64";
|
||||||
else if (arch.equals("arm")) return "armhf";
|
else if (arch.equals("arm")) return "armhf";
|
||||||
@@ -72,11 +75,11 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
|||||||
TorPlugin createPluginInstance(Backoff backoff,
|
TorPlugin createPluginInstance(Backoff backoff,
|
||||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||||
String architecture) {
|
String architecture) {
|
||||||
return new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
|
TorWrapper tor = new UnixTorWrapper(ioExecutor, eventExecutor,
|
||||||
networkManager, locationUtils, torSocketFactory, clock,
|
architecture, torDirectory, torSocksPort, torControlPort);
|
||||||
resourceProvider, circumventionProvider, batteryManager,
|
return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
|
||||||
backoff, torRendezvousCrypto, callback, architecture,
|
locationUtils, torSocketFactory, circumventionProvider,
|
||||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
batteryManager, backoff, torRendezvousCrypto, tor, callback,
|
||||||
torControlPort);
|
MAX_LATENCY, MAX_IDLE_TIME, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
|||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
@@ -13,8 +14,9 @@ import org.briarproject.bramble.api.plugin.TorDirectory;
|
|||||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
|
||||||
|
import org.briarproject.bramble.plugin.tor.wrapper.WindowsTorWrapper;
|
||||||
import org.briarproject.nullsafety.NotNullByDefault;
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -34,13 +36,13 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
|
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
|
@EventExecutor Executor eventExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
SocketFactory torSocketFactory,
|
SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory,
|
BackoffFactory backoffFactory,
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
@@ -48,8 +50,8 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
|
|||||||
@TorDirectory File torDirectory,
|
@TorDirectory File torDirectory,
|
||||||
@TorSocksPort int torSocksPort,
|
@TorSocksPort int torSocksPort,
|
||||||
@TorControlPort int torControlPort) {
|
@TorControlPort int torControlPort) {
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||||
circumventionProvider, batteryManager, clock, crypto,
|
circumventionProvider, batteryManager, clock, crypto,
|
||||||
torDirectory, torSocksPort, torControlPort);
|
torDirectory, torSocksPort, torControlPort);
|
||||||
}
|
}
|
||||||
@@ -70,11 +72,11 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
|
|||||||
TorPlugin createPluginInstance(Backoff backoff,
|
TorPlugin createPluginInstance(Backoff backoff,
|
||||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||||
String architecture) {
|
String architecture) {
|
||||||
return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
|
TorWrapper tor = new WindowsTorWrapper(ioExecutor, eventExecutor,
|
||||||
networkManager, locationUtils, torSocketFactory, clock,
|
architecture, torDirectory, torSocksPort, torControlPort);
|
||||||
resourceProvider, circumventionProvider, batteryManager,
|
return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
|
||||||
backoff, torRendezvousCrypto, callback, architecture,
|
locationUtils, torSocketFactory, circumventionProvider,
|
||||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
batteryManager, backoff, torRendezvousCrypto, tor, callback,
|
||||||
torControlPort);
|
MAX_LATENCY, MAX_IDLE_TIME, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor.wrapper;
|
||||||
|
|
||||||
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.security.CodeSource;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
abstract class JavaTorWrapper extends AbstractTorWrapper {
|
||||||
|
|
||||||
|
JavaTorWrapper(Executor ioExecutor,
|
||||||
|
Executor eventExecutor,
|
||||||
|
String architecture,
|
||||||
|
File torDirectory,
|
||||||
|
int torSocksPort,
|
||||||
|
int torControlPort) {
|
||||||
|
super(ioExecutor, eventExecutor, architecture, torDirectory,
|
||||||
|
torSocksPort, torControlPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long getLastUpdateTime() {
|
||||||
|
CodeSource codeSource =
|
||||||
|
getClass().getProtectionDomain().getCodeSource();
|
||||||
|
if (codeSource == null) throw new AssertionError("CodeSource null");
|
||||||
|
try {
|
||||||
|
URI path = codeSource.getLocation().toURI();
|
||||||
|
File file = new File(path);
|
||||||
|
return file.lastModified();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream getResourceInputStream(String name,
|
||||||
|
String extension) {
|
||||||
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
|
return requireNonNull(cl.getResourceAsStream(name + extension));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor.wrapper;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
|
||||||
|
import org.briarproject.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class UnixTorWrapper extends JavaTorWrapper {
|
||||||
|
|
||||||
|
public UnixTorWrapper(Executor ioExecutor,
|
||||||
|
Executor eventExecutor,
|
||||||
|
String architecture,
|
||||||
|
File torDirectory,
|
||||||
|
int torSocksPort,
|
||||||
|
int torControlPort) {
|
||||||
|
super(ioExecutor, eventExecutor, architecture, torDirectory,
|
||||||
|
torSocksPort, torControlPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getProcessId() {
|
||||||
|
return CLibrary.INSTANCE.getpid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface CLibrary extends Library {
|
||||||
|
|
||||||
|
CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class);
|
||||||
|
|
||||||
|
int getpid();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +1,29 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
package org.briarproject.bramble.plugin.tor.wrapper;
|
||||||
|
|
||||||
import com.sun.jna.platform.win32.Kernel32;
|
import com.sun.jna.platform.win32.Kernel32;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginException;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
|
||||||
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.Scanner;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class WindowsTorPlugin extends JavaTorPlugin {
|
public class WindowsTorWrapper extends JavaTorWrapper {
|
||||||
|
|
||||||
WindowsTorPlugin(Executor ioExecutor,
|
public WindowsTorWrapper(Executor ioExecutor,
|
||||||
Executor wakefulIoExecutor,
|
Executor eventExecutor,
|
||||||
NetworkManager networkManager,
|
|
||||||
LocationUtils locationUtils,
|
|
||||||
SocketFactory torSocketFactory,
|
|
||||||
Clock clock,
|
|
||||||
ResourceProvider resourceProvider,
|
|
||||||
CircumventionProvider circumventionProvider,
|
|
||||||
BatteryManager batteryManager,
|
|
||||||
Backoff backoff,
|
|
||||||
TorRendezvousCrypto torRendezvousCrypto,
|
|
||||||
PluginCallback callback,
|
|
||||||
String architecture,
|
String architecture,
|
||||||
long maxLatency,
|
|
||||||
int maxIdleTime,
|
|
||||||
File torDirectory,
|
File torDirectory,
|
||||||
int torSocksPort,
|
int torSocksPort,
|
||||||
int torControlPort) {
|
int torControlPort) {
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
super(ioExecutor, eventExecutor, architecture, torDirectory,
|
||||||
torSocketFactory, clock, resourceProvider,
|
torSocksPort, torControlPort);
|
||||||
circumventionProvider, batteryManager, backoff,
|
|
||||||
torRendezvousCrypto, callback, architecture,
|
|
||||||
maxLatency, maxIdleTime, torDirectory, torSocksPort,
|
|
||||||
torControlPort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,7 +33,7 @@ class WindowsTorPlugin extends JavaTorPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void waitForTorToStart(Process torProcess)
|
protected void waitForTorToStart(Process torProcess)
|
||||||
throws InterruptedException, PluginException {
|
throws InterruptedException, IOException {
|
||||||
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
|
// 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
|
// Wait for the control port to be opened, then continue to read its
|
||||||
// stdout and stderr in a background thread until it exits.
|
// stdout and stderr in a background thread until it exits.
|
||||||
@@ -91,7 +66,7 @@ class WindowsTorPlugin extends JavaTorPlugin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Wait for the startup result
|
// Wait for the startup result
|
||||||
if (!success.take()) throw new PluginException();
|
if (!success.take()) throw new IOException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Multiset;
|
|||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
@@ -113,6 +114,9 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
@IoExecutor
|
@IoExecutor
|
||||||
Executor ioExecutor;
|
Executor ioExecutor;
|
||||||
@Inject
|
@Inject
|
||||||
|
@EventExecutor
|
||||||
|
Executor eventExecutor;
|
||||||
|
@Inject
|
||||||
@WakefulIoExecutor
|
@WakefulIoExecutor
|
||||||
Executor wakefulIoExecutor;
|
Executor wakefulIoExecutor;
|
||||||
@Inject
|
@Inject
|
||||||
@@ -185,9 +189,9 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
return singletonList(params.bridge);
|
return singletonList(params.bridge);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
|
factory = new UnixTorPluginFactory(ioExecutor, eventExecutor,
|
||||||
networkManager, locationUtils, eventBus, torSocketFactory,
|
wakefulIoExecutor, networkManager, locationUtils, eventBus,
|
||||||
backoffFactory, resourceProvider, bridgeProvider,
|
torSocketFactory, backoffFactory, bridgeProvider,
|
||||||
batteryManager, clock, crypto, torDir, torSocksPort,
|
batteryManager, clock, crypto, torDir, torSocksPort,
|
||||||
torControlPort);
|
torControlPort);
|
||||||
}
|
}
|
||||||
@@ -207,7 +211,7 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
DuplexPlugin duplexPlugin =
|
DuplexPlugin duplexPlugin =
|
||||||
factory.createPlugin(new TestPluginCallback());
|
factory.createPlugin(new TestPluginCallback());
|
||||||
assertNotNull(duplexPlugin);
|
assertNotNull(duplexPlugin);
|
||||||
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
|
TorPlugin plugin = (TorPlugin) duplexPlugin;
|
||||||
|
|
||||||
LOG.warning("Testing " + params.bridge);
|
LOG.warning("Testing " + params.bridge);
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user