mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-21 15:19:53 +01:00
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.
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
ControlPort 59051
|
ControlPort 59051
|
||||||
CookieAuthentication 1
|
CookieAuthentication 1
|
||||||
DisableNetwork 1
|
DisableNetwork 1
|
||||||
PidFile pid
|
|
||||||
RunAsDaemon 1
|
RunAsDaemon 1
|
||||||
SafeSocks 1
|
SafeSocks 1
|
||||||
SocksPort 59050
|
SocksPort 59050
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ public class SplashScreenActivity extends RoboSplashActivity {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(SplashScreenActivity.class.getName());
|
Logger.getLogger(SplashScreenActivity.class.getName());
|
||||||
|
|
||||||
// This build expires on 17 May 2014
|
// This build expires on 12 July 2014
|
||||||
private static final long EXPIRY_DATE = 1400284800 * 1000L;
|
private static final long EXPIRY_DATE = 1405123200 * 1000L;
|
||||||
|
|
||||||
private long now = System.currentTimeMillis();
|
private long now = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -61,6 +61,7 @@ public class SplashScreenActivity extends RoboSplashActivity {
|
|||||||
setContentView(layout);
|
setContentView(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void startNextActivity() {
|
protected void startNextActivity() {
|
||||||
long duration = System.currentTimeMillis() - now;
|
long duration = System.currentTimeMillis() - now;
|
||||||
if(LOG.isLoggable(INFO))
|
if(LOG.isLoggable(INFO))
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
import org.briarproject.api.android.AndroidExecutor;
|
import org.briarproject.api.android.AndroidExecutor;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.lifecycle.ShutdownManager;
|
|
||||||
import org.briarproject.api.plugins.PluginExecutor;
|
import org.briarproject.api.plugins.PluginExecutor;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
|
import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
|
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
|
||||||
@@ -26,6 +25,7 @@ import com.google.inject.Provides;
|
|||||||
|
|
||||||
public class AndroidPluginsModule extends AbstractModule {
|
public class AndroidPluginsModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void configure() {}
|
protected void configure() {}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -41,14 +41,13 @@ public class AndroidPluginsModule extends AbstractModule {
|
|||||||
DuplexPluginConfig getDuplexPluginConfig(
|
DuplexPluginConfig getDuplexPluginConfig(
|
||||||
@PluginExecutor Executor pluginExecutor,
|
@PluginExecutor Executor pluginExecutor,
|
||||||
AndroidExecutor androidExecutor, Application app,
|
AndroidExecutor androidExecutor, Application app,
|
||||||
CryptoComponent crypto, LocationUtils locationUtils,
|
CryptoComponent crypto, LocationUtils locationUtils) {
|
||||||
ShutdownManager shutdownManager) {
|
|
||||||
Context appContext = app.getApplicationContext();
|
Context appContext = app.getApplicationContext();
|
||||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(
|
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(
|
||||||
pluginExecutor, androidExecutor, appContext,
|
pluginExecutor, androidExecutor, appContext,
|
||||||
crypto.getSecureRandom());
|
crypto.getSecureRandom());
|
||||||
DuplexPluginFactory tor = new TorPluginFactory(pluginExecutor,
|
DuplexPluginFactory tor = new TorPluginFactory(pluginExecutor,
|
||||||
appContext, locationUtils, shutdownManager);
|
appContext, locationUtils);
|
||||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(
|
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(
|
||||||
pluginExecutor, appContext);
|
pluginExecutor, appContext);
|
||||||
final Collection<DuplexPluginFactory> factories =
|
final Collection<DuplexPluginFactory> factories =
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import org.briarproject.api.TransportConfig;
|
|||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.TransportProperties;
|
import org.briarproject.api.TransportProperties;
|
||||||
import org.briarproject.api.crypto.PseudoRandom;
|
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.DuplexPlugin;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||||
@@ -64,6 +63,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
|
|||||||
private static final String[] EVENTS = {
|
private static final String[] EVENTS = {
|
||||||
"CIRC", "ORCONN", "NOTICE", "WARN", "ERR"
|
"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 SOCKS_PORT = 59050, CONTROL_PORT = 59051;
|
||||||
private static final int COOKIE_TIMEOUT = 3000; // Milliseconds
|
private static final int COOKIE_TIMEOUT = 3000; // Milliseconds
|
||||||
private static final int HOSTNAME_TIMEOUT = 30 * 1000; // 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 Executor pluginExecutor;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final ShutdownManager shutdownManager;
|
|
||||||
private final DuplexPluginCallback callback;
|
private final DuplexPluginCallback callback;
|
||||||
private final int maxFrameLength;
|
private final int maxFrameLength;
|
||||||
private final long maxLatency, pollingInterval;
|
private final long maxLatency, pollingInterval;
|
||||||
private final File torDirectory, torFile, geoIpFile, configFile, doneFile;
|
private final File torDirectory, torFile, geoIpFile, configFile, doneFile;
|
||||||
private final File cookieFile, pidFile, hostnameFile;
|
private final File cookieFile, hostnameFile;
|
||||||
private final AtomicBoolean circuitBuilt;
|
private final AtomicBoolean circuitBuilt;
|
||||||
|
|
||||||
private volatile boolean running = false, networkEnabled = false;
|
private volatile boolean running = false, networkEnabled = false;
|
||||||
private volatile boolean bootstrapped = false;
|
private volatile boolean bootstrapped = false;
|
||||||
private volatile Process tor = null;
|
|
||||||
private volatile int pid = -1;
|
|
||||||
private volatile ServerSocket socket = null;
|
private volatile ServerSocket socket = null;
|
||||||
private volatile Socket controlSocket = null;
|
private volatile Socket controlSocket = null;
|
||||||
private volatile TorControlConnection controlConnection = null;
|
private volatile TorControlConnection controlConnection = null;
|
||||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||||
|
|
||||||
TorPlugin(Executor pluginExecutor, Context appContext,
|
TorPlugin(Executor pluginExecutor, Context appContext,
|
||||||
LocationUtils locationUtils, ShutdownManager shutdownManager,
|
LocationUtils locationUtils, DuplexPluginCallback callback,
|
||||||
DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
|
int maxFrameLength, long maxLatency, long pollingInterval) {
|
||||||
long pollingInterval) {
|
|
||||||
this.pluginExecutor = pluginExecutor;
|
this.pluginExecutor = pluginExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.shutdownManager = shutdownManager;
|
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.maxFrameLength = maxFrameLength;
|
this.maxFrameLength = maxFrameLength;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
@@ -110,7 +105,6 @@ class TorPlugin implements DuplexPlugin, EventHandler {
|
|||||||
configFile = new File(torDirectory, "torrc");
|
configFile = new File(torDirectory, "torrc");
|
||||||
doneFile = new File(torDirectory, "done");
|
doneFile = new File(torDirectory, "done");
|
||||||
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
||||||
pidFile = new File(torDirectory, ".tor/pid");
|
|
||||||
hostnameFile = new File(torDirectory, "hostname");
|
hostnameFile = new File(torDirectory, "hostname");
|
||||||
circuitBuilt = new AtomicBoolean(false);
|
circuitBuilt = new AtomicBoolean(false);
|
||||||
}
|
}
|
||||||
@@ -133,17 +127,9 @@ class TorPlugin implements DuplexPlugin, EventHandler {
|
|||||||
try {
|
try {
|
||||||
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
|
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
|
||||||
LOG.info("Tor is already running");
|
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) {
|
} catch(IOException e) {
|
||||||
LOG.info("Tor is not running");
|
LOG.info("Tor is not running");
|
||||||
startProcess = true;
|
startProcess = true;
|
||||||
}
|
|
||||||
if(startProcess) {
|
|
||||||
// Install the binary, possibly overwriting an older version
|
// Install the binary, possibly overwriting an older version
|
||||||
if(!installBinary()) {
|
if(!installBinary()) {
|
||||||
LOG.warning("Could not install Tor binary");
|
LOG.warning("Could not install Tor binary");
|
||||||
@@ -164,23 +150,25 @@ class TorPlugin implements DuplexPlugin, EventHandler {
|
|||||||
// Start a new Tor process
|
// Start a new Tor process
|
||||||
String torPath = torFile.getAbsolutePath();
|
String torPath = torFile.getAbsolutePath();
|
||||||
String configPath = configFile.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() };
|
String[] env = { "HOME=" + torDirectory.getAbsolutePath() };
|
||||||
|
Process torProcess;
|
||||||
try {
|
try {
|
||||||
tor = Runtime.getRuntime().exec(cmd, env, torDirectory);
|
torProcess = Runtime.getRuntime().exec(cmd, env, torDirectory);
|
||||||
} catch(SecurityException e1) {
|
} catch(SecurityException e1) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e1.toString(), e1);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e1.toString(), e1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Log the process's standard output until it detaches
|
// Log the process's standard output until it detaches
|
||||||
if(LOG.isLoggable(INFO)) {
|
if(LOG.isLoggable(INFO)) {
|
||||||
Scanner stdout = new Scanner(tor.getInputStream());
|
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||||
while(stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
while(stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||||
stdout.close();
|
stdout.close();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Wait for the process to detach or exit
|
// Wait for the process to detach or exit
|
||||||
int exit = tor.waitFor();
|
int exit = torProcess.waitFor();
|
||||||
if(exit != 0) {
|
if(exit != 0) {
|
||||||
if(LOG.isLoggable(WARNING))
|
if(LOG.isLoggable(WARNING))
|
||||||
LOG.warning("Tor exited with value " + exit);
|
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);
|
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
|
||||||
}
|
}
|
||||||
running = true;
|
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
|
// Open a control connection and authenticate using the cookie file
|
||||||
controlConnection = new TorControlConnection(controlSocket);
|
controlConnection = new TorControlConnection(controlSocket);
|
||||||
controlConnection.authenticate(read(cookieFile));
|
controlConnection.authenticate(read(cookieFile));
|
||||||
// Tell Tor to exit when the control connection is closed
|
// Tell Tor to exit when the control connection is closed
|
||||||
controlConnection.takeOwnership();
|
controlConnection.takeOwnership();
|
||||||
|
controlConnection.resetConf(Arrays.asList(OWNER));
|
||||||
// Register to receive events from the Tor process
|
// Register to receive events from the Tor process
|
||||||
controlConnection.setEventHandler(this);
|
controlConnection.setEventHandler(this);
|
||||||
controlConnection.setEvents(Arrays.asList(EVENTS));
|
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() {
|
private void bind() {
|
||||||
pluginExecutor.execute(new Runnable() {
|
pluginExecutor.execute(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -581,8 +484,6 @@ class TorPlugin implements DuplexPlugin, EventHandler {
|
|||||||
controlSocket.close();
|
controlSocket.close();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
killTorProcess();
|
|
||||||
killZombieProcess();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.util.concurrent.Executor;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.lifecycle.ShutdownManager;
|
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
|
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
|
||||||
@@ -25,14 +24,12 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final Executor pluginExecutor;
|
private final Executor pluginExecutor;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final ShutdownManager shutdownManager;
|
|
||||||
|
|
||||||
public TorPluginFactory(Executor pluginExecutor, Context appContext,
|
public TorPluginFactory(Executor pluginExecutor, Context appContext,
|
||||||
LocationUtils locationUtils, ShutdownManager shutdownManager) {
|
LocationUtils locationUtils) {
|
||||||
this.pluginExecutor = pluginExecutor;
|
this.pluginExecutor = pluginExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.shutdownManager = shutdownManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransportId getId() {
|
public TransportId getId() {
|
||||||
@@ -46,7 +43,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new TorPlugin(pluginExecutor,appContext, locationUtils,
|
return new TorPlugin(pluginExecutor,appContext, locationUtils,
|
||||||
shutdownManager, callback, MAX_FRAME_LENGTH, MAX_LATENCY,
|
callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
|
||||||
POLLING_INTERVAL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user