From d406853f68163061d64e9930b44563126614cc66 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Tue, 8 Jul 2014 22:35:37 +0100 Subject: [PATCH] Shut down the Tor process without hacks. Tor has a controller command, TAKEOWNERSHIP, and a configuration option, __OwningControllerProcess, that work together to ensure Tor shuts down when the controlling process dies and/or disconnects from the control port. By using them we can avoid creating runaway Tor processes that have to be killed with hacks. --- briar-android/assets/torrc | 1 - .../android/SplashScreenActivity.java | 5 +- .../plugins/AndroidPluginsModule.java | 7 +- .../briarproject/plugins/tor/TorPlugin.java | 121 ++---------------- .../plugins/tor/TorPluginFactory.java | 8 +- 5 files changed, 19 insertions(+), 123 deletions(-) diff --git a/briar-android/assets/torrc b/briar-android/assets/torrc index ce4eb5618d..3d27a7f205 100644 --- a/briar-android/assets/torrc +++ b/briar-android/assets/torrc @@ -1,7 +1,6 @@ ControlPort 59051 CookieAuthentication 1 DisableNetwork 1 -PidFile pid RunAsDaemon 1 SafeSocks 1 SocksPort 59050 diff --git a/briar-android/src/org/briarproject/android/SplashScreenActivity.java b/briar-android/src/org/briarproject/android/SplashScreenActivity.java index d85e061097..ff48381cd3 100644 --- a/briar-android/src/org/briarproject/android/SplashScreenActivity.java +++ b/briar-android/src/org/briarproject/android/SplashScreenActivity.java @@ -30,8 +30,8 @@ public class SplashScreenActivity extends RoboSplashActivity { private static final Logger LOG = Logger.getLogger(SplashScreenActivity.class.getName()); - // This build expires on 17 May 2014 - private static final long EXPIRY_DATE = 1400284800 * 1000L; + // This build expires on 12 July 2014 + private static final long EXPIRY_DATE = 1405123200 * 1000L; private long now = System.currentTimeMillis(); @@ -61,6 +61,7 @@ public class SplashScreenActivity extends RoboSplashActivity { setContentView(layout); } + @Override protected void startNextActivity() { long duration = System.currentTimeMillis() - now; if(LOG.isLoggable(INFO)) diff --git a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java index 2228b8b5b1..25f6951282 100644 --- a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java +++ b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java @@ -7,7 +7,6 @@ import java.util.concurrent.Executor; import org.briarproject.api.android.AndroidExecutor; import org.briarproject.api.crypto.CryptoComponent; -import org.briarproject.api.lifecycle.ShutdownManager; import org.briarproject.api.plugins.PluginExecutor; import org.briarproject.api.plugins.duplex.DuplexPluginConfig; import org.briarproject.api.plugins.duplex.DuplexPluginFactory; @@ -26,6 +25,7 @@ import com.google.inject.Provides; public class AndroidPluginsModule extends AbstractModule { + @Override protected void configure() {} @Provides @@ -41,14 +41,13 @@ public class AndroidPluginsModule extends AbstractModule { DuplexPluginConfig getDuplexPluginConfig( @PluginExecutor Executor pluginExecutor, AndroidExecutor androidExecutor, Application app, - CryptoComponent crypto, LocationUtils locationUtils, - ShutdownManager shutdownManager) { + CryptoComponent crypto, LocationUtils locationUtils) { Context appContext = app.getApplicationContext(); DuplexPluginFactory bluetooth = new DroidtoothPluginFactory( pluginExecutor, androidExecutor, appContext, crypto.getSecureRandom()); DuplexPluginFactory tor = new TorPluginFactory(pluginExecutor, - appContext, locationUtils, shutdownManager); + appContext, locationUtils); DuplexPluginFactory lan = new AndroidLanTcpPluginFactory( pluginExecutor, appContext); final Collection<DuplexPluginFactory> factories = diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index 7ac2f53946..c8b500975e 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -38,7 +38,6 @@ import org.briarproject.api.TransportConfig; import org.briarproject.api.TransportId; import org.briarproject.api.TransportProperties; import org.briarproject.api.crypto.PseudoRandom; -import org.briarproject.api.lifecycle.ShutdownManager; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexTransportConnection; @@ -64,6 +63,7 @@ class TorPlugin implements DuplexPlugin, EventHandler { private static final String[] EVENTS = { "CIRC", "ORCONN", "NOTICE", "WARN", "ERR" }; + private static final String OWNER = "__OwningControllerProcess"; private static final int SOCKS_PORT = 59050, CONTROL_PORT = 59051; private static final int COOKIE_TIMEOUT = 3000; // Milliseconds private static final int HOSTNAME_TIMEOUT = 30 * 1000; // Milliseconds @@ -75,31 +75,26 @@ class TorPlugin implements DuplexPlugin, EventHandler { private final Executor pluginExecutor; private final Context appContext; private final LocationUtils locationUtils; - private final ShutdownManager shutdownManager; private final DuplexPluginCallback callback; private final int maxFrameLength; private final long maxLatency, pollingInterval; private final File torDirectory, torFile, geoIpFile, configFile, doneFile; - private final File cookieFile, pidFile, hostnameFile; + private final File cookieFile, hostnameFile; private final AtomicBoolean circuitBuilt; private volatile boolean running = false, networkEnabled = false; private volatile boolean bootstrapped = false; - private volatile Process tor = null; - private volatile int pid = -1; private volatile ServerSocket socket = null; private volatile Socket controlSocket = null; private volatile TorControlConnection controlConnection = null; private volatile BroadcastReceiver networkStateReceiver = null; TorPlugin(Executor pluginExecutor, Context appContext, - LocationUtils locationUtils, ShutdownManager shutdownManager, - DuplexPluginCallback callback, int maxFrameLength, long maxLatency, - long pollingInterval) { + LocationUtils locationUtils, DuplexPluginCallback callback, + int maxFrameLength, long maxLatency, long pollingInterval) { this.pluginExecutor = pluginExecutor; this.appContext = appContext; this.locationUtils = locationUtils; - this.shutdownManager = shutdownManager; this.callback = callback; this.maxFrameLength = maxFrameLength; this.maxLatency = maxLatency; @@ -110,7 +105,6 @@ class TorPlugin implements DuplexPlugin, EventHandler { configFile = new File(torDirectory, "torrc"); doneFile = new File(torDirectory, "done"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); - pidFile = new File(torDirectory, ".tor/pid"); hostnameFile = new File(torDirectory, "hostname"); circuitBuilt = new AtomicBoolean(false); } @@ -133,17 +127,9 @@ class TorPlugin implements DuplexPlugin, EventHandler { try { controlSocket = new Socket("127.0.0.1", CONTROL_PORT); LOG.info("Tor is already running"); - if(readPidFile() == -1) { - LOG.info("Could not read PID of Tor process"); - controlSocket.close(); - killZombieProcess(); - startProcess = true; - } } catch(IOException e) { LOG.info("Tor is not running"); startProcess = true; - } - if(startProcess) { // Install the binary, possibly overwriting an older version if(!installBinary()) { LOG.warning("Could not install Tor binary"); @@ -164,23 +150,25 @@ class TorPlugin implements DuplexPlugin, EventHandler { // Start a new Tor process String torPath = torFile.getAbsolutePath(); String configPath = configFile.getAbsolutePath(); - String[] cmd = { torPath, "-f", configPath }; + String pid = String.valueOf(android.os.Process.myPid()); + String[] cmd = { torPath, "-f", configPath, OWNER, pid }; String[] env = { "HOME=" + torDirectory.getAbsolutePath() }; + Process torProcess; try { - tor = Runtime.getRuntime().exec(cmd, env, torDirectory); + torProcess = Runtime.getRuntime().exec(cmd, env, torDirectory); } catch(SecurityException e1) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e1.toString(), e1); return false; } // Log the process's standard output until it detaches if(LOG.isLoggable(INFO)) { - Scanner stdout = new Scanner(tor.getInputStream()); + Scanner stdout = new Scanner(torProcess.getInputStream()); while(stdout.hasNextLine()) LOG.info(stdout.nextLine()); stdout.close(); } try { // Wait for the process to detach or exit - int exit = tor.waitFor(); + int exit = torProcess.waitFor(); if(exit != 0) { if(LOG.isLoggable(WARNING)) LOG.warning("Tor exited with value " + exit); @@ -201,20 +189,12 @@ class TorPlugin implements DuplexPlugin, EventHandler { controlSocket = new Socket("127.0.0.1", CONTROL_PORT); } running = true; - // Read the PID of the Tor process so we can kill it if necessary - pid = readPidFile(); - // Create a shutdown hook to ensure the Tor process is killed - shutdownManager.addShutdownHook(new Runnable() { - public void run() { - killTorProcess(); - killZombieProcess(); - } - }); // Open a control connection and authenticate using the cookie file controlConnection = new TorControlConnection(controlSocket); controlConnection.authenticate(read(cookieFile)); // Tell Tor to exit when the control connection is closed controlConnection.takeOwnership(); + controlConnection.resetConf(Arrays.asList(OWNER)); // Register to receive events from the Tor process controlConnection.setEventHandler(this); controlConnection.setEvents(Arrays.asList(EVENTS)); @@ -370,83 +350,6 @@ class TorPlugin implements DuplexPlugin, EventHandler { } } - private int readPidFile() { - // Read the PID of the Tor process so we can kill it if necessary - try { - return Integer.parseInt(new String(read(pidFile), "UTF-8").trim()); - } catch(IOException e) { - LOG.warning("Could not read PID file"); - } catch(NumberFormatException e) { - LOG.warning("Could not parse PID file"); - } - return -1; - } - - /* - * If the app crashes, leaving a Tor process running, and the user clears - * the app's data, removing the PID file and auth cookie file, it's no - * longer possible to communicate with the zombie process and it must be - * killed. ActivityManager.killBackgroundProcesses() doesn't seem to work - * in this case, so we must parse the output of ps to get the PID. - * <p> - * On all devices we've tested, the output consists of a header line - * followed by one line per process. The second column is the PID and the - * last column is the process name, which includes the app's package name. - * On some devices tested by the Guardian Project, the first column is the - * PID. - */ - private void killZombieProcess() { - String packageName = "/" + appContext.getPackageName() + "/"; - try { - // Parse the output of ps - Process ps = Runtime.getRuntime().exec("ps"); - Scanner scanner = new Scanner(ps.getInputStream()); - // Discard the header line - if(scanner.hasNextLine()) scanner.nextLine(); - // Look for any Tor processes with our package name - boolean found = false; - while(scanner.hasNextLine()) { - String[] columns = scanner.nextLine().split("\\s+"); - if(columns.length < 3) continue; - int pid; - try { - pid = Integer.parseInt(columns[1]); - } catch(NumberFormatException e) { - try { - pid = Integer.parseInt(columns[0]); - } catch(NumberFormatException e1) { - continue; - } - } - String name = columns[columns.length - 1]; - if(name.contains(packageName) && name.endsWith("/tor")) { - if(LOG.isLoggable(INFO)) - LOG.info("Killing zombie process " + pid); - android.os.Process.killProcess(pid); - found = true; - } - } - if(!found) LOG.info("No zombies found"); - scanner.close(); - } catch(IOException e) { - LOG.warning("Could not parse ps output"); - } catch(SecurityException e) { - LOG.warning("Could not execute ps"); - } - } - - private void killTorProcess() { - if(tor != null) { - LOG.info("Killing Tor via destroy()"); - tor.destroy(); - } - if(pid != -1) { - if(LOG.isLoggable(INFO)) - LOG.info("Killing Tor via killProcess(" + pid + ")"); - android.os.Process.killProcess(pid); - } - } - private void bind() { pluginExecutor.execute(new Runnable() { public void run() { @@ -581,8 +484,6 @@ class TorPlugin implements DuplexPlugin, EventHandler { controlSocket.close(); } catch(IOException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - killTorProcess(); - killZombieProcess(); } } diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java index 0990b6acab..e520e25673 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java @@ -4,7 +4,6 @@ import java.util.concurrent.Executor; import java.util.logging.Logger; import org.briarproject.api.TransportId; -import org.briarproject.api.lifecycle.ShutdownManager; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexPluginFactory; @@ -25,14 +24,12 @@ public class TorPluginFactory implements DuplexPluginFactory { private final Executor pluginExecutor; private final Context appContext; private final LocationUtils locationUtils; - private final ShutdownManager shutdownManager; public TorPluginFactory(Executor pluginExecutor, Context appContext, - LocationUtils locationUtils, ShutdownManager shutdownManager) { + LocationUtils locationUtils) { this.pluginExecutor = pluginExecutor; this.appContext = appContext; this.locationUtils = locationUtils; - this.shutdownManager = shutdownManager; } public TransportId getId() { @@ -46,7 +43,6 @@ public class TorPluginFactory implements DuplexPluginFactory { return null; } return new TorPlugin(pluginExecutor,appContext, locationUtils, - shutdownManager, callback, MAX_FRAME_LENGTH, MAX_LATENCY, - POLLING_INTERVAL); + callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL); } } -- GitLab