Move Tor wrapper to library.

This commit is contained in:
akwizgran
2023-03-28 15:39:47 +01:00
parent 06dd8c65aa
commit 61e7d2ebf9
37 changed files with 39 additions and 2225 deletions

View File

@@ -3,9 +3,9 @@ package org.briarproject.bramble;
import org.briarproject.bramble.io.DnsModule;
import org.briarproject.bramble.mailbox.ModularMailboxModule;
import org.briarproject.bramble.network.JavaNetworkModule;
import org.briarproject.bramble.plugin.tor.wrapper.CircumventionModule;
import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.system.JavaSystemModule;
import org.briarproject.onionwrapper.CircumventionModule;
import dagger.Module;

View File

@@ -15,10 +15,10 @@ import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
import org.briarproject.bramble.plugin.tor.wrapper.UnixTorWrapper;
import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.TorWrapper;
import org.briarproject.onionwrapper.UnixTorWrapper;
import java.io.File;
import java.util.concurrent.Executor;

View File

@@ -15,10 +15,10 @@ import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
import org.briarproject.bramble.plugin.tor.wrapper.WindowsTorWrapper;
import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.TorWrapper;
import org.briarproject.onionwrapper.WindowsTorWrapper;
import java.io.File;
import java.util.concurrent.Executor;

View File

@@ -1,47 +0,0 @@
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));
}
}

View File

@@ -1,53 +0,0 @@
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;
/**
* A Tor wrapper for Unix-like operating systems.
*/
@NotNullByDefault
public class UnixTorWrapper extends JavaTorWrapper {
/**
* @param ioExecutor The wrapper will use this executor to run IO tasks,
* some of which may run for the lifetime of the wrapper, so the executor
* should have an unlimited thread pool.
* @param eventExecutor The wrapper will use this executor to call the
* {@link Observer observer} (if any). To ensure that events are observed
* in the order they occur, this executor should have a single thread (eg
* the app's main thread).
* @param architecture The processor architecture of the Tor and pluggable
* transport binaries.
* @param torDirectory The directory where the Tor process should keep its
* state.
* @param torSocksPort The port number to use for Tor's SOCKS port.
* @param torControlPort The port number to use for Tor's control port.
*/
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();
}
}

View File

@@ -1,95 +0,0 @@
package org.briarproject.bramble.plugin.tor.wrapper;
import com.sun.jna.platform.win32.Kernel32;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import static java.util.logging.Level.INFO;
/**
* A Tor wrapper for the Windows operating system.
*/
@NotNullByDefault
public class WindowsTorWrapper extends JavaTorWrapper {
/**
* @param ioExecutor The wrapper will use this executor to run IO tasks,
* some of which may run for the lifetime of the wrapper, so the executor
* should have an unlimited thread pool.
* @param eventExecutor The wrapper will use this executor to call
* @param eventExecutor The wrapper will use this executor to call the
* {@link Observer observer} (if any). To ensure that events are observed
* in the order they occur, this executor should have a single thread (eg
* the app's main thread).
* @param architecture The processor architecture of the Tor and pluggable
* transport binaries.
* @param torDirectory The directory where the Tor process should keep its
* state.
* @param torSocksPort The port number to use for Tor's SOCKS port.
* @param torControlPort The port number to use for Tor's control port.
*/
public WindowsTorWrapper(Executor ioExecutor,
Executor eventExecutor,
String architecture,
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, eventExecutor, architecture, torDirectory,
torSocksPort, torControlPort);
}
@Override
protected int getProcessId() {
return Kernel32.INSTANCE.GetCurrentProcessId();
}
@Override
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, IOException {
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
// Wait for the control port to be opened, then continue to read its
// stdout and stderr in a background thread until it exits.
BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
ioExecutor.execute(() -> {
boolean started = false;
// Read the process's stdout (and redirected stderr)
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Startup has succeeded when the control port is open
while (stdout.hasNextLine()) {
String line = stdout.nextLine();
if (!started && line.contains("Opened Control listener")) {
success.add(true);
started = true;
}
}
stdout.close();
// If the control port wasn't opened, startup has failed
if (!started) success.add(false);
// Wait for the process to exit
try {
int exit = torProcess.waitFor();
if (LOG.isLoggable(INFO))
LOG.info("Tor exited with value " + exit);
} catch (InterruptedException e1) {
LOG.warning("Interrupted while waiting for Tor to exit");
Thread.currentThread().interrupt();
}
});
// Wait for the startup result
if (!success.take()) throw new IOException();
}
@Override
protected String getExecutableExtension() {
return ".exe";
}
}

View File

@@ -1,288 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
import org.briarproject.bramble.api.Multiset;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
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.network.NetworkManager;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.system.Clock;
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.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType;
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
import org.briarproject.bramble.util.OsUtils;
import org.briarproject.nullsafety.NotNullByDefault;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public class BridgeTest extends BrambleTestCase {
private static final String[] SNOWFLAKE_COUNTRY_CODES = {"TM", "ZZ"};
@Parameters
public static Iterable<Params> data() {
BrambleJavaIntegrationTestComponent component =
DaggerBrambleJavaIntegrationTestComponent.builder().build();
BrambleCoreIntegrationTestEagerSingletons.Helper
.injectEagerSingletons(component);
// Share stats among all the test instances
Stats stats = new Stats();
CircumventionProvider provider = component.getCircumventionProvider();
List<Params> states = new ArrayList<>();
for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) {
for (String bridge :
provider.getBridges(DEFAULT_OBFS4, "", true)) {
states.add(new Params(bridge, DEFAULT_OBFS4, stats, false));
}
for (String bridge :
provider.getBridges(NON_DEFAULT_OBFS4, "", true)) {
states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats,
false));
}
for (String bridge : provider.getBridges(VANILLA, "", true)) {
states.add(new Params(bridge, VANILLA, stats, false));
}
for (String bridge : provider.getBridges(MEEK, "", true)) {
states.add(new Params(bridge, MEEK, stats, true));
}
for (String countryCode : SNOWFLAKE_COUNTRY_CODES) {
for (String bridge :
provider.getBridges(SNOWFLAKE, countryCode, true)) {
states.add(new Params(bridge, SNOWFLAKE, stats, true));
}
for (String bridge :
provider.getBridges(SNOWFLAKE, countryCode, false)) {
states.add(new Params(bridge, SNOWFLAKE, stats, true));
}
}
}
return states;
}
private final static long TIMEOUT = MINUTES.toMillis(2);
private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
private final static int ATTEMPTS_PER_BRIDGE = 5;
private final static Logger LOG = getLogger(BridgeTest.class.getName());
@Inject
@IoExecutor
Executor ioExecutor;
@Inject
@EventExecutor
Executor eventExecutor;
@Inject
@WakefulIoExecutor
Executor wakefulIoExecutor;
@Inject
NetworkManager networkManager;
@Inject
ResourceProvider resourceProvider;
@Inject
BatteryManager batteryManager;
@Inject
EventBus eventBus;
@Inject
BackoffFactory backoffFactory;
@Inject
Clock clock;
@Inject
CryptoComponent crypto;
@Inject
@TorSocksPort
int torSocksPort;
@Inject
@TorControlPort
int torControlPort;
private final File torDir = getTestDirectory();
private final Params params;
private UnixTorPluginFactory factory;
public BridgeTest(Params params) {
this.params = params;
}
@Before
public void setUp() {
// Skip this test unless it's explicitly enabled in the environment
assumeTrue(isOptionalTestEnabled(BridgeTest.class));
// TODO: Remove this assumption when the plugin supports other platforms
assumeTrue(OsUtils.isLinux());
BrambleJavaIntegrationTestComponent component =
DaggerBrambleJavaIntegrationTestComponent.builder().build();
BrambleCoreIntegrationTestEagerSingletons.Helper
.injectEagerSingletons(component);
component.inject(this);
LocationUtils locationUtils = () -> "US";
SocketFactory torSocketFactory = SocketFactory.getDefault();
@NotNullByDefault
CircumventionProvider bridgeProvider = new CircumventionProvider() {
@Override
public boolean isTorProbablyBlocked(String countryCode) {
return true;
}
@Override
public boolean doBridgesWork(String countryCode) {
return true;
}
@Override
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
return singletonList(params.bridgeType);
}
@Override
public List<String> getBridges(BridgeType bridgeType,
String countryCode, boolean letsEncrypt) {
return singletonList(params.bridge);
}
};
factory = new UnixTorPluginFactory(ioExecutor, eventExecutor,
wakefulIoExecutor, networkManager, locationUtils, eventBus,
torSocketFactory, backoffFactory, bridgeProvider,
batteryManager, clock, crypto, torDir, torSocksPort,
torControlPort);
}
@After
public void tearDown() {
deleteTestDirectory(torDir);
}
@Test
public void testBridges() throws Exception {
if (params.stats.hasSucceeded(params.bridge)) {
LOG.info("Skipping previously successful bridge: " + params.bridge);
return;
}
DuplexPlugin duplexPlugin =
factory.createPlugin(new TestPluginCallback());
assertNotNull(duplexPlugin);
TorPlugin plugin = (TorPlugin) duplexPlugin;
LOG.warning("Testing " + params.bridge);
try {
plugin.start();
long start = clock.currentTimeMillis();
long timeout = params.bridgeType == MEEK ? MEEK_TIMEOUT : TIMEOUT;
while (clock.currentTimeMillis() - start < timeout) {
if (plugin.getState() == ACTIVE) break;
clock.sleep(500);
}
if (plugin.getState() == ACTIVE) {
LOG.info("Connected to Tor: " + params.bridge);
params.stats.countSuccess(params.bridge);
} else {
LOG.warning("Could not connect to Tor within timeout: "
+ params.bridge);
params.stats.countFailure(params.bridge, params.essential);
}
} finally {
plugin.stop();
}
}
private static class Params {
private final String bridge;
private final BridgeType bridgeType;
private final Stats stats;
private final boolean essential;
private Params(String bridge, BridgeType bridgeType,
Stats stats, boolean essential) {
this.bridge = bridge;
this.bridgeType = bridgeType;
this.stats = stats;
this.essential = essential;
}
}
private static class Stats {
@GuardedBy("this")
private final Set<String> successes = new HashSet<>();
@GuardedBy("this")
private final Multiset<String> failures = new Multiset<>();
@GuardedBy("this")
private final Set<String> unreachable = new TreeSet<>();
private synchronized boolean hasSucceeded(String bridge) {
return successes.contains(bridge);
}
private synchronized void countSuccess(String bridge) {
successes.add(bridge);
}
private synchronized void countFailure(String bridge,
boolean essential) {
if (failures.add(bridge) == ATTEMPTS_PER_BRIDGE) {
LOG.warning("Bridge is unreachable after "
+ ATTEMPTS_PER_BRIDGE + " attempts: " + bridge);
unreachable.add(bridge);
if (unreachable.size() > UNREACHABLE_BRIDGES_ALLOWED) {
fail(unreachable.size() + " bridges are unreachable: "
+ unreachable);
}
if (essential) {
fail("essential bridge is unreachable");
}
}
}
}
}

View File

@@ -1,57 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.Collection;
import static java.util.Collections.emptyList;
@NotNullByDefault
public class TestPluginCallback implements PluginCallback {
@Override
public Settings getSettings() {
return new Settings();
}
@Override
public TransportProperties getLocalProperties() {
return new TransportProperties();
}
@Override
public Collection<TransportProperties> getRemoteProperties() {
return emptyList();
}
@Override
public void mergeSettings(Settings s) {
}
@Override
public void mergeLocalProperties(TransportProperties p) {
}
@Override
public void pluginStateChanged(State state) {
}
@Override
public void handleConnection(DuplexTransportConnection c) {
}
@Override
public void handleReader(TransportConnectionReader r) {
}
@Override
public void handleWriter(TransportConnectionWriter w) {
}
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.BrambleJavaModule;
import org.briarproject.bramble.mailbox.ModularMailboxModule;
import org.briarproject.bramble.plugin.tor.BridgeTest;
import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class,
BrambleJavaModule.class,
ModularMailboxModule.class,
TestTorPortsModule.class,
TestPluginConfigModule.class,
})
public interface BrambleJavaIntegrationTestComponent
extends BrambleCoreIntegrationTestEagerSingletons {
void inject(BridgeTest init);
CircumventionProvider getCircumventionProvider();
}

View File

@@ -1,40 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.util.OsUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeTrue;
public class TestResources {
@Before
public void isRunningOnLinux() {
assumeTrue(OsUtils.isLinux());
}
@Test
public void canReadTorLinux() {
InputStream input = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("x86_64/tor");
assertNotNull(input);
}
@Test
public void canReadObfs4ProxyLinux() {
InputStream input = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("x86_64/obfs4proxy");
assertNotNull(input);
}
@Test
public void canReadSnowflakeLinux() {
InputStream input = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("x86_64/snowflake");
assertNotNull(input);
}
}

View File

@@ -1,26 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
@Module
class TestTorPortsModule {
@Provides
@TorSocksPort
int provideTorSocksPort() {
return DEFAULT_SOCKS_PORT + 10;
}
@Provides
@TorControlPort
int provideTorControlPort() {
return DEFAULT_CONTROL_PORT + 10;
}
}