From a468af94dbbeb19b893c51d8c3431c1007df9afc Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 28 Mar 2023 11:31:20 +0100 Subject: [PATCH] Add bootstrap percentage and HS desc uploads to observer interface. --- .../plugin/tor/wrapper/AndroidTorWrapper.java | 8 +- .../bramble/plugin/tor/TorPlugin.java | 21 +++- .../tor/wrapper/AbstractTorWrapper.java | 102 ++++++++++++------ .../plugin/tor/wrapper/TorWrapper.java | 23 ++-- .../plugin/tor/wrapper/UnixTorWrapper.java | 8 +- .../plugin/tor/wrapper/WindowsTorWrapper.java | 7 +- 6 files changed, 113 insertions(+), 56 deletions(-) diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AndroidTorWrapper.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AndroidTorWrapper.java index b07b4e147..3788ce923 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AndroidTorWrapper.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AndroidTorWrapper.java @@ -55,10 +55,10 @@ public class AndroidTorWrapper extends AbstractTorWrapper { * @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 - * {@link StateObserver#observeState(TorState)}. To ensure that state - * changes are observed in the order they occur, this executor should have - * a single thread (eg the app's main thread). + * @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 diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index d95d24dc4..655567d34 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -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.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; @@ -148,10 +149,22 @@ class TorPlugin implements DuplexPlugin, EventListener { // Don't execute more than one connection status check at a time connectionStatusExecutor = new PoliteExecutor("TorPlugin", ioExecutor, 1); - tor.setStateObserver(torState -> { - State s = state.getState(torState); - if (s == ACTIVE) backoff.reset(); - callback.pluginStateChanged(s); + tor.setObserver(new Observer() { + + @Override + 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) { + } }); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AbstractTorWrapper.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AbstractTorWrapper.java index 95c52dbd0..dc8f36c9b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AbstractTorWrapper.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AbstractTorWrapper.java @@ -21,6 +21,8 @@ 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; @@ -61,6 +63,8 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { 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; @@ -112,8 +116,8 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { } @Override - public void setStateObserver(@Nullable StateObserver stateObserver) { - state.setObserver(stateObserver); + public void setObserver(@Nullable Observer observer) { + state.setObserver(observer); } @Override @@ -172,9 +176,10 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { 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(); + 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"); @@ -410,7 +415,9 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { 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])); + 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) { - if (msg.startsWith("BOOTSTRAP PROGRESS=100")) { - LOG.info("Bootstrapped"); - state.setBootstrapped(); + 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")) { @@ -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) { if (msg.startsWith("CLOCK_JUMPED")) { Long time = parseLongArgument(msg, "TIME"); @@ -512,7 +535,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { @GuardedBy("this") @Nullable - private StateObserver observer = null; + private Observer observer = null; @GuardedBy("this") private boolean started = false, @@ -521,9 +544,14 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { networkEnabled = false, paddingEnabled = false, ipv6Enabled = false, - bootstrapped = false, circuitBuilt = false; + @GuardedBy("this") + private int bootstrapPercentage = 0; + + @GuardedBy("this") + private List bridges = emptyList(); + @GuardedBy("this") private int orConnectionsConnected = 0; @@ -532,32 +560,25 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { private TorState state = null; private synchronized void setObserver( - @Nullable StateObserver observer) { + @Nullable Observer observer) { this.observer = observer; } @GuardedBy("this") - private void updateObserver() { + private void updateState() { 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)); + // Notify the observer on the event executor + eventExecutor.execute(() -> observer.onState(newState)); } } } - @GuardedBy("this") - private List bridges = emptyList(); - private synchronized void setStarted() { started = true; - updateObserver(); + updateState(); } @SuppressWarnings("BooleanMethodIsAlwaysInverted") @@ -567,13 +588,18 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { private synchronized void setStopped() { stopped = true; - updateObserver(); + updateState(); } - private synchronized void setBootstrapped() { - if (bootstrapped) return; - bootstrapped = true; - updateObserver(); + 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(); } /** @@ -583,7 +609,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { private synchronized boolean setCircuitBuilt(boolean built) { if (built == circuitBuilt) return false; // Unchanged circuitBuilt = built; - updateObserver(); + updateState(); return true; // Changed } @@ -598,7 +624,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { networkEnabled = enable; if (!enable) circuitBuilt = false; if (!wasInitialised || enable != wasEnabled) { - updateObserver(); + updateState(); } return enable != wasEnabled; } @@ -639,15 +665,15 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { if (!started || stopped) return STARTING_STOPPING; if (!networkInitialised) return CONNECTING; if (!networkEnabled) return DISABLED; - return bootstrapped && circuitBuilt && orConnectionsConnected > 0 - ? CONNECTED : CONNECTING; + return bootstrapPercentage == 100 && circuitBuilt + && orConnectionsConnected > 0 ? CONNECTED : CONNECTING; } private synchronized void onOrConnectionConnected() { int oldConnected = orConnectionsConnected; orConnectionsConnected++; logOrConnections(); - if (oldConnected == 0) updateObserver(); + if (oldConnected == 0) updateState(); } private synchronized void onOrConnectionClosed() { @@ -659,7 +685,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { } logOrConnections(); if (orConnectionsConnected == 0 && oldConnected != 0) { - updateObserver(); + updateState(); } } @@ -669,5 +695,13 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper { 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)); + } + } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorWrapper.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorWrapper.java index 8618c8424..01a94fe23 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorWrapper.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorWrapper.java @@ -34,7 +34,7 @@ public interface TorWrapper { * existing observer, or removes any existing observer if the argument is * null. */ - void setStateObserver(@Nullable StateObserver stateObserver); + void setObserver(@Nullable Observer observer); /** * 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 - * 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. - * The call happens on the event executor supplied to the wrapper's - * constructor. + * Called whenever the state of the Tor process changes. */ - 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 { diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/UnixTorWrapper.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/UnixTorWrapper.java index d01550c67..d6f599c78 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/UnixTorWrapper.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/UnixTorWrapper.java @@ -18,10 +18,10 @@ 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 - * {@link StateObserver#observeState(TorState)}. To ensure that state - * changes are observed in the order they occur, this executor should have - * a single thread (eg the app's main thread). + * @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 diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/WindowsTorWrapper.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/WindowsTorWrapper.java index fb1169411..3807786b5 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/WindowsTorWrapper.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/WindowsTorWrapper.java @@ -24,9 +24,10 @@ public class WindowsTorWrapper extends JavaTorWrapper { * 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 - * {@link StateObserver#observeState(TorState)}. To ensure that state - * changes are observed in the order they occur, this executor should have - * a single thread (eg the app's main thread). + * @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