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

@@ -10,6 +10,7 @@ apply from: '../dagger.gradle'
dependencies {
api project(':bramble-api')
api project(':onionwrapper-core')
api 'org.briarproject:jtorctl:0.5'

View File

@@ -25,14 +25,14 @@ import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType;
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.Observer;
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState;
import org.briarproject.nullsafety.InterfaceNotNullByDefault;
import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.CircumventionProvider.BridgeType;
import org.briarproject.onionwrapper.TorWrapper;
import org.briarproject.onionwrapper.TorWrapper.HiddenServiceProperties;
import org.briarproject.onionwrapper.TorWrapper.Observer;
import org.briarproject.onionwrapper.TorWrapper.TorState;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -78,12 +78,12 @@ import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
@InterfaceNotNullByDefault
class TorPlugin implements DuplexPlugin, EventListener {

View File

@@ -19,8 +19,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
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.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import java.io.File;
import java.util.concurrent.Executor;

View File

@@ -1,707 +0,0 @@
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 java.util.regex.Matcher;
import java.util.regex.Pattern;
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;
private static final Pattern BOOTSTRAP_PERCENTAGE =
Pattern.compile(".*PROGRESS=(\\d{1,3}).*");
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 setObserver(@Nullable Observer observer) {
state.setObserver(observer);
}
@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=")) {
int percentage = parseBootstrapPercentage(info);
if (percentage == 100) LOG.info("Tor has already bootstrapped");
state.setBootstrapPercentage(percentage);
}
// 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)) {
String onion = parts[1];
LOG.info("V3 descriptor uploaded for " + scrubOnion(onion));
state.onHsDescriptorUploaded(onion);
}
}
}
private String removeSeverity(String msg) {
return msg.replaceFirst("[^ ]+ ", "");
}
private void handleClientStatus(String msg) {
if (msg.startsWith("BOOTSTRAP PROGRESS=")) {
int percentage = parseBootstrapPercentage(msg);
if (percentage == 100) LOG.info("Bootstrapped");
state.setBootstrapPercentage(percentage);
} 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 int parseBootstrapPercentage(String s) {
Matcher matcher = BOOTSTRAP_PERCENTAGE.matcher(s);
if (matcher.matches()) {
try {
return Integer.parseInt(matcher.group(1));
} catch (NumberFormatException e) {
// Fall through
}
}
if (LOG.isLoggable(WARNING)) {
LOG.warning("Failed to parse bootstrap percentage: " + s);
}
return 0;
}
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 Observer observer = null;
@GuardedBy("this")
private boolean started = false,
stopped = false,
networkInitialised = false,
networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
circuitBuilt = false;
@GuardedBy("this")
private int bootstrapPercentage = 0;
@GuardedBy("this")
private List<String> bridges = emptyList();
@GuardedBy("this")
private int orConnectionsConnected = 0;
@GuardedBy("this")
@Nullable
private TorState state = null;
private synchronized void setObserver(
@Nullable Observer observer) {
this.observer = observer;
}
@GuardedBy("this")
private void updateState() {
TorState newState = getState();
if (newState != state) {
state = newState;
if (observer != null) {
// Notify the observer on the event executor
eventExecutor.execute(() -> observer.onState(newState));
}
}
}
private synchronized void setStarted() {
started = true;
updateState();
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private synchronized boolean isTorRunning() {
return started && !stopped;
}
private synchronized void setStopped() {
stopped = true;
updateState();
}
private synchronized void setBootstrapPercentage(int percentage) {
if (percentage == bootstrapPercentage) return;
bootstrapPercentage = percentage;
if (observer != null) {
// Notify the observer on the event executor
eventExecutor.execute(() ->
observer.onBootstrapPercentage(percentage));
}
updateState();
}
/**
* 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;
updateState();
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) {
updateState();
}
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 bootstrapPercentage == 100 && circuitBuilt
&& orConnectionsConnected > 0 ? CONNECTED : CONNECTING;
}
private synchronized void onOrConnectionConnected() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected++;
logOrConnections();
if (oldConnected == 0) updateState();
}
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) {
updateState();
}
}
@GuardedBy("this")
private void logOrConnections() {
if (LOG.isLoggable(INFO)) {
LOG.info(orConnectionsConnected + " OR connections connected");
}
}
private synchronized void onHsDescriptorUploaded(String onion) {
if (observer != null) {
// Notify the observer on the event executor
eventExecutor.execute(() ->
observer.onHsDescriptorUpload(onion));
}
}
}
}

View File

@@ -1,78 +0,0 @@
package org.briarproject.bramble.plugin.tor.wrapper;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public interface CircumventionProvider {
enum BridgeType {
DEFAULT_OBFS4,
NON_DEFAULT_OBFS4,
VANILLA,
MEEK,
SNOWFLAKE
}
/**
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
* <p>
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/
String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #DPI_BRIDGES}.
*/
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] DEFAULT_BRIDGES = {"EG", "VE"};
/**
* Countries where non-default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NON_DEFAULT_BRIDGES = {"BY", "RU"};
/**
* Countries where vanilla bridges are blocked via DPI but non-default
* obfs4 bridges, meek and snowflake may work. Should be a subset of
* {@link #BRIDGES}.
*/
String[] DPI_BRIDGES = {"CN", "IR", "TM"};
/**
* Returns true if vanilla Tor connections are blocked in the given country.
*/
boolean isTorProbablyBlocked(String countryCode);
/**
* Returns true if bridge connections of some type work in the given
* country.
*/
boolean doBridgesWork(String countryCode);
/**
* Returns the types of bridge connection that are suitable for the given
* country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
* to work.
*/
List<BridgeType> getSuitableBridgeTypes(String countryCode);
@IoExecutor
List<String> getBridges(BridgeType type, String countryCode,
boolean letsEncrypt);
}

View File

@@ -1,129 +0,0 @@
package org.briarproject.bramble.plugin.tor.wrapper;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Arrays.asList;
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.nullsafety.NullSafety.requireNonNull;
@Immutable
@NotNullByDefault
class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges";
private final static String SNOWFLAKE_PARAMS_FILE_NAME = "snowflake-params";
private final static String DEFAULT_COUNTRY_CODE = "ZZ";
private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(asList(BLOCKED));
private static final Set<String> BRIDGE_COUNTRIES =
new HashSet<>(asList(BRIDGES));
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> DPI_COUNTRIES =
new HashSet<>(asList(DPI_BRIDGES));
@Inject
CircumventionProviderImpl() {
}
@Override
public boolean isTorProbablyBlocked(String countryCode) {
return BLOCKED_IN_COUNTRIES.contains(countryCode);
}
@Override
public boolean doBridgesWork(String countryCode) {
return BRIDGE_COUNTRIES.contains(countryCode);
}
@Override
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE);
} else {
return asList(DEFAULT_OBFS4, VANILLA);
}
}
@Override
@IoExecutor
public List<String> getBridges(BridgeType type, String countryCode,
boolean letsEncrypt) {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME));
Scanner scanner = new Scanner(is);
List<String> bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
(type == VANILLA && line.startsWith("v ")) ||
(type == MEEK && line.startsWith("m "))) {
bridges.add(line.substring(2));
} else if (type == SNOWFLAKE && line.startsWith("s ")) {
String params = getSnowflakeParams(countryCode, letsEncrypt);
bridges.add(line.substring(2) + " " + params);
}
}
scanner.close();
return bridges;
}
// Package access for testing
@SuppressWarnings("WeakerAccess")
String getSnowflakeParams(String countryCode, boolean letsEncrypt) {
Map<String, String> params = loadSnowflakeParams();
if (countryCode.isEmpty()) countryCode = DEFAULT_COUNTRY_CODE;
// If we have parameters for this country code, return them
String value = params.get(makeKey(countryCode, letsEncrypt));
if (value != null) return value;
// Return the default parameters
value = params.get(makeKey(DEFAULT_COUNTRY_CODE, letsEncrypt));
return requireNonNull(value);
}
private Map<String, String> loadSnowflakeParams() {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(SNOWFLAKE_PARAMS_FILE_NAME));
Scanner scanner = new Scanner(is);
Map<String, String> params = new TreeMap<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.length() < 5) continue;
String key = line.substring(0, 4); // Country code, space, digit
String value = line.substring(5);
params.put(key, value);
}
scanner.close();
return params;
}
private String makeKey(String countryCode, boolean letsEncrypt) {
return countryCode + " " + (letsEncrypt ? "1" : "0");
}
}

View File

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

View File

@@ -1,162 +0,0 @@
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 setObserver(@Nullable Observer observer);
/**
* 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. All calls happen on the event executor supplied to the
* wrapper's constructor.
*/
interface Observer {
/**
* Called whenever the state of the Tor process changes.
*/
void onState(TorState s);
/**
* Called whenever the bootstrap percentage changes.
*/
void onBootstrapPercentage(int percentage);
/**
* Called whenever a hidden service descriptor is uploaded.
*/
void onHsDescriptorUpload(String onion);
}
class HiddenServiceProperties {
public final String onion, privKey;
HiddenServiceProperties(String onion, String privKey) {
this.onion = onion;
this.privKey = privKey;
}
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.plugin.tor.wrapper;
package org.briarproject.onionwrapper;
import javax.inject.Singleton;

View File

@@ -1,35 +0,0 @@
d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
n Bridge obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0
n Bridge obfs4 152.70.180.20:1993 3327C43587E66AD5F874C4234A1D72C938AD7318 cert=s7xLRUO2psaX7TMUP2YhXdxItR4U6K7D+E3gQaS/+yWUppevtazIibq4dN1g5Reu6dD2QQ iat-mode=0
n Bridge obfs4 144.202.12.254:10002 4E220F45CD404C8A3082A36326A5ED19BB8D4404 cert=iLz5YYWO4pUw7U7MRNOSvE0qO+IVeE4kVfFVWPO3coH3FmZtrkvlaTklfXxHZaCcXWBgaA iat-mode=0
n Bridge obfs4 109.14.168.159:5082 BFE1416DEFFE969581F016A4A319A87FFB26BA91 cert=n3X1CDdKBPXPIzfKh83p3ydfMzb0AD9gKC+/gIpHb7+xjjAnYO9x3LT+T/MvOIfAXxYySg iat-mode=0
n Bridge obfs4 185.177.207.132:8443 4FB781F7A9DD39DA53A7996907817FC479874D19 cert=UL2gCAXWW5kEWY4TQ0lNeu6OAmzh40bXYVhMnTWVG8USnyy/zEKGSIPgmwTDMumWr9c1Pg iat-mode=0
n Bridge obfs4 213.108.110.149:7499 519344140473CF91030B08F91521F9A6C144ED6C cert=k9fSL/d491qAkGmi2VeSwVlfuyO02jBeN54qxzzQISxpfm3b+a6kJpo8/Bfy1ACbHZIJUg iat-mode=0
n Bridge obfs4 158.174.114.97:3456 32665CD4CBE19092CA47A53D317B8BFF5810441C cert=ne5Zt0TcMedSGmFwAs/AV6J6E9Hn7mG5mR6vQNpEfyuCZK1VRpQvU1LvvtesSu4CXqZtYQ iat-mode=0
n Bridge obfs4 64.4.175.62:8000 8B72740D150795ACB5101AA5F95D1ACDA4FE6B3E cert=vduuNhJ5U/8hjZmllP6AFfXSlSZsnrimdR8Tm8DY9dxWS4n2j92fNc0qHihUwRqwcOfIcg iat-mode=0
n Bridge obfs4 82.64.115.17:990 B08238781C2CD80DBD95AEABEB6F6C75F2E2CEB6 cert=1udeMlFNs3sJ20zwpPE6nShZqqwDb3F1ET4KzfSfD+fktkue9zNx9H3t+yLCPAsg+6UTUA iat-mode=1
n Bridge obfs4 87.161.120.147:9292 9418EEBE8AEAE32CC381AF51610366E8B24651E0 cert=DFRm74qsD1i2/ypaGochpX6CS1j9JTFAKEYaHXrgrx6M2LG5Cvppdt3Ob7lULfhqgtAUdg iat-mode=0
n Bridge obfs4 157.90.245.231:8599 C23CD468EC04555E2B37BE81A771E681049DEA6A cert=UsmDelrbwg4jc6BMvZJ0TS8klUIa2qkbRu3xwQc3ZXPEgpMqyTYUxcVwyPbIU5KmAHsmAA iat-mode=0
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB
v Bridge 75.100.92.154:22815 465E990FA8A752DDE861EDF31E42454ACC037AB4
v Bridge 87.100.193.2:9010 13FB63452AADFA082BD2BC3E1E320AD301F07877
v Bridge 65.21.240.163:33245 20BD59649212CFE7412BFC9B94C3CCCFD8F807A8
m Bridge meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com utls=hellochrome_auto
s Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72

View File

@@ -1,4 +0,0 @@
ZZ 1 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
ZZ 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
TM 1 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto
TM 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto

View File

@@ -1,92 +0,0 @@
package org.briarproject.bramble.plugin.tor.wrapper;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BLOCKED;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BRIDGES;
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.plugin.tor.wrapper.CircumventionProvider.DEFAULT_BRIDGES;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.DPI_BRIDGES;
import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.NON_DEFAULT_BRIDGES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
public class CircumventionProviderImplTest extends BrambleTestCase {
private final CircumventionProviderImpl provider =
new CircumventionProviderImpl();
@Test
public void testInvariants() {
Set<String> blocked = new HashSet<>(asList(BLOCKED));
Set<String> bridges = new HashSet<>(asList(BRIDGES));
Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
Set<String> nonDefaultBridges =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
Set<String> dpiBridges = new HashSet<>(asList(DPI_BRIDGES));
// BRIDGES should be a subset of BLOCKED
assertTrue(blocked.containsAll(bridges));
// BRIDGES should be the union of the bridge type sets
Set<String> union = new HashSet<>(defaultBridges);
union.addAll(nonDefaultBridges);
union.addAll(dpiBridges);
assertEquals(bridges, union);
// The bridge type sets should not overlap
assertEmptyIntersection(defaultBridges, nonDefaultBridges);
assertEmptyIntersection(defaultBridges, dpiBridges);
assertEmptyIntersection(nonDefaultBridges, dpiBridges);
}
@Test
public void testGetBestBridgeType() {
for (String country : DEFAULT_BRIDGES) {
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
provider.getSuitableBridgeTypes(country));
}
for (String country : NON_DEFAULT_BRIDGES) {
assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
provider.getSuitableBridgeTypes(country));
}
for (String country : DPI_BRIDGES) {
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE),
provider.getSuitableBridgeTypes(country));
}
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
provider.getSuitableBridgeTypes("ZZ"));
}
@Test
public void testHasSnowflakeParamsWithLetsEncrypt() {
testHasSnowflakeParams(true);
}
@Test
public void testHasSnowflakeParamsWithoutLetsEncrypt() {
testHasSnowflakeParams(false);
}
private void testHasSnowflakeParams(boolean letsEncrypt) {
String tmParams = provider.getSnowflakeParams("TM", letsEncrypt);
String defaultParams = provider.getSnowflakeParams("ZZ", letsEncrypt);
assertFalse(tmParams.isEmpty());
assertFalse(defaultParams.isEmpty());
assertNotEquals(defaultParams, tmParams);
}
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
Set<T> intersection = new HashSet<>(a);
intersection.retainAll(b);
assertTrue(intersection.isEmpty());
}
}