Add bootstrap percentage and HS desc uploads to observer interface.

This commit is contained in:
akwizgran
2023-03-28 11:31:20 +01:00
parent 49f10e7e82
commit a468af94db
6 changed files with 113 additions and 56 deletions

View File

@@ -55,10 +55,10 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
* @param ioExecutor The wrapper will use this executor to run IO tasks, * @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 * some of which may run for the lifetime of the wrapper, so the executor
* should have an unlimited thread pool. * 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 StateObserver#observeState(TorState)}. To ensure that state * {@link Observer observer} (if any). To ensure that events are observed
* changes are observed in the order they occur, this executor should have * in the order they occur, this executor should have a single thread (eg
* a single thread (eg the app's main thread). * the app's main thread).
* @param architecture The processor architecture of the Tor and pluggable * @param architecture The processor architecture of the Tor and pluggable
* transport binaries. * transport binaries.
* @param torDirectory The directory where the Tor process should keep its * @param torDirectory The directory where the Tor process should keep its

View File

@@ -28,6 +28,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType; import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper; 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.HiddenServiceProperties;
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.Observer;
import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState; import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState;
import org.briarproject.nullsafety.InterfaceNotNullByDefault; import org.briarproject.nullsafety.InterfaceNotNullByDefault;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
@@ -148,10 +149,22 @@ class TorPlugin implements DuplexPlugin, EventListener {
// 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 -> { tor.setObserver(new Observer() {
State s = state.getState(torState);
if (s == ACTIVE) backoff.reset(); @Override
callback.pluginStateChanged(s); public void onState(TorState torState) {
State s = state.getState(torState);
if (s == ACTIVE) backoff.reset();
callback.pluginStateChanged(s);
}
@Override
public void onBootstrapPercentage(int percentage) {
}
@Override
public void onHsDescriptorUpload(String onion) {
}
}); });
} }

View File

@@ -21,6 +21,8 @@ import java.util.Map;
import java.util.Scanner; 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.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
@@ -61,6 +63,8 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private static final String OWNER = "__OwningControllerProcess"; private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000; private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern BOOTSTRAP_PERCENTAGE =
Pattern.compile("PROGRESS=(\\d{1,3})");
protected final Executor ioExecutor; protected final Executor ioExecutor;
protected final Executor eventExecutor; protected final Executor eventExecutor;
@@ -112,8 +116,8 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
@Override @Override
public void setStateObserver(@Nullable StateObserver stateObserver) { public void setObserver(@Nullable Observer observer) {
state.setObserver(stateObserver); state.setObserver(observer);
} }
@Override @Override
@@ -172,9 +176,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
controlConnection.setEvents(asList(EVENTS)); controlConnection.setEvents(asList(EVENTS));
// Check whether Tor has already bootstrapped // Check whether Tor has already bootstrapped
String info = controlConnection.getInfo("status/bootstrap-phase"); String info = controlConnection.getInfo("status/bootstrap-phase");
if (info != null && info.contains("PROGRESS=100")) { if (info != null && info.contains("PROGRESS=")) {
LOG.info("Tor has already bootstrapped"); int percentage = parseBootstrapPercentage(info);
state.setBootstrapped(); if (percentage == 100) LOG.info("Tor has already bootstrapped");
state.setBootstrapPercentage(percentage);
} }
// Check whether Tor has already built a circuit // Check whether Tor has already built a circuit
info = controlConnection.getInfo("status/circuit-established"); info = controlConnection.getInfo("status/circuit-established");
@@ -410,7 +415,9 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
if (parts.length < 2) { if (parts.length < 2) {
LOG.warning("Failed to parse HS_DESC UPLOADED event"); LOG.warning("Failed to parse HS_DESC UPLOADED event");
} else if (LOG.isLoggable(INFO)) { } else if (LOG.isLoggable(INFO)) {
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1])); String onion = parts[1];
LOG.info("V3 descriptor uploaded for " + scrubOnion(onion));
state.onHsDescriptorUploaded(onion);
} }
} }
} }
@@ -420,9 +427,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
private void handleClientStatus(String msg) { private void handleClientStatus(String msg) {
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) { if (msg.startsWith("BOOTSTRAP PROGRESS=")) {
LOG.info("Bootstrapped"); int percentage = parseBootstrapPercentage(msg);
state.setBootstrapped(); if (percentage == 100) LOG.info("Bootstrapped");
state.setBootstrapPercentage(percentage);
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (state.setCircuitBuilt(true)) LOG.info("Circuit built"); if (state.setCircuitBuilt(true)) LOG.info("Circuit built");
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
@@ -435,6 +443,21 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
} }
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) { private void handleGeneralStatus(String msg) {
if (msg.startsWith("CLOCK_JUMPED")) { if (msg.startsWith("CLOCK_JUMPED")) {
Long time = parseLongArgument(msg, "TIME"); Long time = parseLongArgument(msg, "TIME");
@@ -512,7 +535,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
@GuardedBy("this") @GuardedBy("this")
@Nullable @Nullable
private StateObserver observer = null; private Observer observer = null;
@GuardedBy("this") @GuardedBy("this")
private boolean started = false, private boolean started = false,
@@ -521,9 +544,14 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
networkEnabled = false, networkEnabled = false,
paddingEnabled = false, paddingEnabled = false,
ipv6Enabled = false, ipv6Enabled = false,
bootstrapped = false,
circuitBuilt = false; circuitBuilt = false;
@GuardedBy("this")
private int bootstrapPercentage = 0;
@GuardedBy("this")
private List<String> bridges = emptyList();
@GuardedBy("this") @GuardedBy("this")
private int orConnectionsConnected = 0; private int orConnectionsConnected = 0;
@@ -532,32 +560,25 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private TorState state = null; private TorState state = null;
private synchronized void setObserver( private synchronized void setObserver(
@Nullable StateObserver observer) { @Nullable Observer observer) {
this.observer = observer; this.observer = observer;
} }
@GuardedBy("this") @GuardedBy("this")
private void updateObserver() { private void updateState() {
TorState newState = getState(); TorState newState = getState();
if (newState != state) { if (newState != state) {
state = newState; 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) { if (observer != null) {
eventExecutor.execute(() -> // Notify the observer on the event executor
observer.observeState(newState)); eventExecutor.execute(() -> observer.onState(newState));
} }
} }
} }
@GuardedBy("this")
private List<String> bridges = emptyList();
private synchronized void setStarted() { private synchronized void setStarted() {
started = true; started = true;
updateObserver(); updateState();
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
@@ -567,13 +588,18 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private synchronized void setStopped() { private synchronized void setStopped() {
stopped = true; stopped = true;
updateObserver(); updateState();
} }
private synchronized void setBootstrapped() { private synchronized void setBootstrapPercentage(int percentage) {
if (bootstrapped) return; if (percentage == bootstrapPercentage) return;
bootstrapped = true; bootstrapPercentage = percentage;
updateObserver(); if (observer != null) {
// Notify the observer on the event executor
eventExecutor.execute(() ->
observer.onBootstrapPercentage(percentage));
}
updateState();
} }
/** /**
@@ -583,7 +609,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
private synchronized boolean setCircuitBuilt(boolean built) { private synchronized boolean setCircuitBuilt(boolean built) {
if (built == circuitBuilt) return false; // Unchanged if (built == circuitBuilt) return false; // Unchanged
circuitBuilt = built; circuitBuilt = built;
updateObserver(); updateState();
return true; // Changed return true; // Changed
} }
@@ -598,7 +624,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
networkEnabled = enable; networkEnabled = enable;
if (!enable) circuitBuilt = false; if (!enable) circuitBuilt = false;
if (!wasInitialised || enable != wasEnabled) { if (!wasInitialised || enable != wasEnabled) {
updateObserver(); updateState();
} }
return enable != wasEnabled; return enable != wasEnabled;
} }
@@ -639,15 +665,15 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
if (!started || stopped) return STARTING_STOPPING; if (!started || stopped) return STARTING_STOPPING;
if (!networkInitialised) return CONNECTING; if (!networkInitialised) return CONNECTING;
if (!networkEnabled) return DISABLED; if (!networkEnabled) return DISABLED;
return bootstrapped && circuitBuilt && orConnectionsConnected > 0 return bootstrapPercentage == 100 && circuitBuilt
? CONNECTED : CONNECTING; && orConnectionsConnected > 0 ? CONNECTED : CONNECTING;
} }
private synchronized void onOrConnectionConnected() { private synchronized void onOrConnectionConnected() {
int oldConnected = orConnectionsConnected; int oldConnected = orConnectionsConnected;
orConnectionsConnected++; orConnectionsConnected++;
logOrConnections(); logOrConnections();
if (oldConnected == 0) updateObserver(); if (oldConnected == 0) updateState();
} }
private synchronized void onOrConnectionClosed() { private synchronized void onOrConnectionClosed() {
@@ -659,7 +685,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
} }
logOrConnections(); logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) { if (orConnectionsConnected == 0 && oldConnected != 0) {
updateObserver(); updateState();
} }
} }
@@ -669,5 +695,13 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
LOG.info(orConnectionsConnected + " OR connections connected"); 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

@@ -34,7 +34,7 @@ public interface TorWrapper {
* existing observer, or removes any existing observer if the argument is * existing observer, or removes any existing observer if the argument is
* null. * null.
*/ */
void setStateObserver(@Nullable StateObserver stateObserver); void setObserver(@Nullable Observer observer);
/** /**
* Returns the current state of the wrapper. * Returns the current state of the wrapper.
@@ -129,16 +129,25 @@ public interface TorWrapper {
/** /**
* An interface for observing changes to the {@link TorState state} of the * An interface for observing changes to the {@link TorState state} of the
* Tor process. * Tor process. All calls happen on the event executor supplied to the
* wrapper's constructor.
*/ */
interface StateObserver { interface Observer {
/** /**
* This method is called whenever the state of the Tor process changes. * 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); 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 { class HiddenServiceProperties {

View File

@@ -18,10 +18,10 @@ public class UnixTorWrapper extends JavaTorWrapper {
* @param ioExecutor The wrapper will use this executor to run IO tasks, * @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 * some of which may run for the lifetime of the wrapper, so the executor
* should have an unlimited thread pool. * 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 StateObserver#observeState(TorState)}. To ensure that state * {@link Observer observer} (if any). To ensure that events are observed
* changes are observed in the order they occur, this executor should have * in the order they occur, this executor should have a single thread (eg
* a single thread (eg the app's main thread). * the app's main thread).
* @param architecture The processor architecture of the Tor and pluggable * @param architecture The processor architecture of the Tor and pluggable
* transport binaries. * transport binaries.
* @param torDirectory The directory where the Tor process should keep its * @param torDirectory The directory where the Tor process should keep its

View File

@@ -24,9 +24,10 @@ public class WindowsTorWrapper extends JavaTorWrapper {
* some of which may run for the lifetime of the wrapper, so the executor * some of which may run for the lifetime of the wrapper, so the executor
* should have an unlimited thread pool. * 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
* {@link StateObserver#observeState(TorState)}. To ensure that state * @param eventExecutor The wrapper will use this executor to call the
* changes are observed in the order they occur, this executor should have * {@link Observer observer} (if any). To ensure that events are observed
* a single thread (eg the app's main thread). * 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 * @param architecture The processor architecture of the Tor and pluggable
* transport binaries. * transport binaries.
* @param torDirectory The directory where the Tor process should keep its * @param torDirectory The directory where the Tor process should keep its