From a38933df66960c0c312752d49f50f8f33b19761e Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 15 Apr 2022 10:43:28 +0100 Subject: [PATCH] Read Tor process's stdout until it exits. On Windows, RunAsDaemon is a no-op so we need to read stdout to find out when Tor has finished starting up, then continue to read and discard stdout until Tor exits. --- .../bramble/plugin/tor/TorPlugin.java | 50 +++++++++---------- .../bramble/plugin/tor/WindowsTorPlugin.java | 45 +++++++++++++++++ 2 files changed, 70 insertions(+), 25 deletions(-) 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 72c33030f..164c22c46 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 @@ -107,7 +107,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @ParametersNotNullByDefault abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { - private static final Logger LOG = getLogger(TorPlugin.class.getName()); + protected static final Logger LOG = getLogger(TorPlugin.class.getName()); private static final String[] EVENTS = { "CIRC", @@ -124,7 +124,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}"); - private final Executor ioExecutor, wakefulIoExecutor; + protected final Executor ioExecutor; + private final Executor wakefulIoExecutor; private final Executor connectionStatusExecutor; private final NetworkManager networkManager; private final LocationUtils locationUtils; @@ -260,29 +261,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } catch (SecurityException | IOException e) { throw new PluginException(e); } - // Log the process's standard output until it detaches - if (LOG.isLoggable(INFO)) { - Scanner stdout = new Scanner(torProcess.getInputStream()); - Scanner stderr = new Scanner(torProcess.getErrorStream()); - while (stdout.hasNextLine() || stderr.hasNextLine()) { - if (stdout.hasNextLine()) { - LOG.info(stdout.nextLine()); - } - if (stderr.hasNextLine()) { - LOG.info(stderr.nextLine()); - } - } - stdout.close(); - stderr.close(); - } try { - // Wait for the process to detach or exit - int exit = torProcess.waitFor(); - if (exit != 0) { - if (LOG.isLoggable(WARNING)) - LOG.warning("Tor exited with value " + exit); - throw new PluginException(); - } + // Wait for the Tor process to start + waitForTorToStart(torProcess); // Wait for the auth cookie file to be created/updated long start = clock.currentTimeMillis(); while (cookieFile.length() < 32) { @@ -398,7 +379,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { return zin; } - private static void append(StringBuilder strb, String name, int value) { + private static void append(StringBuilder strb, String name, Object value) { strb.append(name); strb.append(" "); strb.append(value); @@ -406,9 +387,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } 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); @@ -445,6 +428,23 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } } + protected void waitForTorToStart(Process torProcess) + throws InterruptedException, PluginException { + Scanner stdout = new Scanner(torProcess.getInputStream()); + // Log the first line of stdout (contains Tor and library versions) + if (stdout.hasNextLine()) LOG.info(stdout.nextLine()); + // Read the process's stdout (and redirected stderr) until it detaches + while (stdout.hasNextLine()) stdout.nextLine(); + stdout.close(); + // Wait for the process to detach or exit + int exit = torProcess.waitFor(); + if (exit != 0) { + if (LOG.isLoggable(WARNING)) + LOG.warning("Tor exited with value " + exit); + throw new PluginException(); + } + } + private void bind() { ioExecutor.execute(() -> { // If there's already a port number stored in config, reuse it diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPlugin.java index 239a753dc..9f49416fe 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPlugin.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPlugin.java @@ -7,15 +7,21 @@ import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.PluginCallback; +import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.ResourceProvider; import java.io.File; +import java.util.Scanner; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import javax.net.SocketFactory; +import static java.util.logging.Level.INFO; + @NotNullByDefault class WindowsTorPlugin extends JavaTorPlugin { @@ -49,4 +55,43 @@ class WindowsTorPlugin extends JavaTorPlugin { protected int getProcessId() { return Kernel32.INSTANCE.GetCurrentProcessId(); } + + @Override + protected void waitForTorToStart(Process torProcess) + throws InterruptedException, PluginException { + // 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 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 PluginException(); + } + }