diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index 3b8e56cc657b23b6128ba5e71d8b39acb7c94cf2..3abd0feff7800d6c688e43bf796daf793206166a 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -50,8 +50,6 @@ import java.util.List; import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.ZipInputStream; @@ -60,15 +58,13 @@ import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.MODE_PRIVATE; import static android.content.Context.POWER_SERVICE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; -import static android.net.ConnectivityManager.EXTRA_NO_CONNECTIVITY; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -class TorPlugin implements DuplexPlugin, EventHandler, - EventListener { +class TorPlugin implements DuplexPlugin, EventHandler, EventListener { static final TransportId ID = new TransportId("tor"); @@ -93,18 +89,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, private final DuplexPluginCallback callback; private final String architecture; private final int maxLatency, maxIdleTime, pollingInterval, socketTimeout; + private final ConnectionStatus connectionStatus; private final File torDirectory, torFile, geoIpFile, configFile, doneFile; private final File cookieFile, hostnameFile; - private final AtomicBoolean circuitBuilt; - private final AtomicInteger descriptorsPublished; private final PowerManager.WakeLock wakeLock; - private volatile boolean running = false, networkEnabled = false; - private volatile boolean bootstrapped = false; - private volatile boolean connectedToWifi = false; - private volatile boolean online = false; - private volatile long descriptorsPublishedTime = Long.MAX_VALUE; - + private volatile boolean running = false; private volatile ServerSocket socket = null; private volatile Socket controlSocket = null; private volatile TorControlConnection controlConnection = null; @@ -127,6 +117,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, if (maxIdleTime > Integer.MAX_VALUE / 2) socketTimeout = Integer.MAX_VALUE; else socketTimeout = maxIdleTime * 2; + connectionStatus = new ConnectionStatus(pollingInterval); torDirectory = appContext.getDir("tor", MODE_PRIVATE); torFile = new File(torDirectory, "tor"); geoIpFile = new File(torDirectory, "geoip"); @@ -134,8 +125,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, doneFile = new File(torDirectory, "done"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); hostnameFile = new File(torDirectory, "hs/hostname"); - circuitBuilt = new AtomicBoolean(false); - descriptorsPublished = new AtomicInteger(0); Object o = appContext.getSystemService(POWER_SERVICE); PowerManager pm = (PowerManager) o; wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "TorPlugin"); @@ -237,7 +226,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, String phase = controlConnection.getInfo("status/bootstrap-phase"); if (phase != null && phase.contains("PROGRESS=100")) { LOG.info("Tor has already bootstrapped"); - bootstrapped = true; + connectionStatus.setBootstrapped(); sendCrashReports(); } } @@ -495,13 +484,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, private void enableNetwork(boolean enable) throws IOException { if (!running) return; - if (networkEnabled == enable) return; - if (LOG.isLoggable(INFO)) LOG.info("Enabling network: " + enable); if (enable) wakeLock.acquire(); - circuitBuilt.set(false); - descriptorsPublished.set(0); - descriptorsPublishedTime = Long.MAX_VALUE; - networkEnabled = enable; + connectionStatus.enableNetwork(enable); controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); if (!enable) { callback.transportDisabled(); @@ -532,7 +516,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, } public boolean isRunning() { - return running && networkEnabled && bootstrapped && circuitBuilt.get(); + return running && connectionStatus.isConnected(); } public boolean shouldPoll() { @@ -545,16 +529,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void poll(Collection<ContactId> connected) { if (!isRunning()) return; - if (descriptorsPublished.get() >= MIN_DESCRIPTORS_PUBLISHED) { - long now = clock.currentTimeMillis(); - if (now - descriptorsPublishedTime >= 2 * pollingInterval) { - LOG.info("Hidden service descriptor published, not polling"); - return; - } + if (connectionStatus.shouldPoll(clock.currentTimeMillis())) { + // TODO: Pass properties to connectAndCallBack() + for (ContactId c : callback.getRemoteProperties().keySet()) + if (!connected.contains(c)) connectAndCallBack(c); + } else { + LOG.info("Hidden service descriptor published, not polling"); } - // TODO: Pass properties to connectAndCallBack() - for (ContactId c : callback.getRemoteProperties().keySet()) - if (!connected.contains(c)) connectAndCallBack(c); } private void connectAndCallBack(final ContactId c) { @@ -616,7 +597,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, } public void circuitStatus(String status, String id, String path) { - if (status.equals("BUILT") && !circuitBuilt.getAndSet(true)) { + if (status.equals("BUILT") && connectionStatus.setCircuitBuilt()) { LOG.info("First circuit built"); if (isRunning()) callback.transportEnabled(); } @@ -638,20 +619,15 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void message(String severity, String msg) { if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) { - bootstrapped = true; + connectionStatus.setBootstrapped(); sendCrashReports(); if (isRunning()) callback.transportEnabled(); } } public void unrecognized(String type, String msg) { - if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) { - int descriptors = descriptorsPublished.incrementAndGet(); - if (descriptors == MIN_DESCRIPTORS_PUBLISHED) { - LOG.info("Hidden service descriptor published"); - descriptorsPublishedTime = clock.currentTimeMillis(); - } - } + if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) + connectionStatus.descriptorPublished(clock.currentTimeMillis()); } private static class WriteObserver extends FileObserver { @@ -673,7 +649,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void eventOccurred(Event e) { if (e instanceof SettingsUpdatedEvent) { if (((SettingsUpdatedEvent) e).getNamespace().equals("tor")) { - // Wifi setting may have been updated + LOG.info("Tor settings updated"); updateConnectionStatus(); } } @@ -684,17 +660,23 @@ class TorPlugin implements DuplexPlugin, EventHandler, public void run() { if (!running) return; + Object o = appContext.getSystemService(CONNECTIVITY_SERVICE); + ConnectivityManager cm = (ConnectivityManager) o; + NetworkInfo net = cm.getActiveNetworkInfo(); + boolean online = net != null && net.isConnected(); + boolean wifi = online && net.getType() == TYPE_WIFI; String country = locationUtils.getCurrentCountry(); - if (LOG.isLoggable(INFO)) { - LOG.info("Online: " + online); - if ("".equals(country)) LOG.info("Country code unknown"); - else LOG.info("Country code: " + country); - } boolean blocked = TorNetworkMetadata.isTorProbablyBlocked( country); Settings s = callback.getSettings(); boolean useMobileData = s.getBoolean("torOverMobile", true); + if (LOG.isLoggable(INFO)) { + LOG.info("Online: " + online + ", wifi: " + wifi); + if ("".equals(country)) LOG.info("Country code unknown"); + else LOG.info("Country code: " + country); + } + try { if (!online) { LOG.info("Disabling network, device is offline"); @@ -702,10 +684,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, } else if (blocked) { LOG.info("Disabling network, country is blocked"); enableNetwork(false); - } else if (!useMobileData & !connectedToWifi) { + } else if (!wifi && !useMobileData) { LOG.info("Disabling network due to data setting"); enableNetwork(false); } else { + LOG.info("Enabling network"); enableNetwork(true); } } catch (IOException e) { @@ -723,16 +706,55 @@ class TorPlugin implements DuplexPlugin, EventHandler, if (!running) return; if (CONNECTIVITY_ACTION.equals(i.getAction())) { LOG.info("Detected connectivity change"); - online = !i.getBooleanExtra(EXTRA_NO_CONNECTIVITY, false); - // Some devices fail to set EXTRA_NO_CONNECTIVITY, double check - Object o = ctx.getSystemService(CONNECTIVITY_SERVICE); - ConnectivityManager cm = (ConnectivityManager) o; - NetworkInfo net = cm.getActiveNetworkInfo(); - if (net == null || !net.isConnected()) online = false; - connectedToWifi = (net != null && net.getType() == TYPE_WIFI - && net.isConnected()); updateConnectionStatus(); } } } + + private static class ConnectionStatus { + + private final int pollingInterval; + + // All of the following are locking: this + private boolean networkEnabled = false; + private boolean bootstrapped = false, circuitBuilt = false; + private int descriptorsPublished = 0; + private long descriptorsPublishedTime = Long.MAX_VALUE; + + private ConnectionStatus(int pollingInterval) { + this.pollingInterval = pollingInterval; + } + + private synchronized void setBootstrapped() { + bootstrapped = true; + } + + private synchronized boolean setCircuitBuilt() { + boolean firstCircuit = !circuitBuilt; + circuitBuilt = true; + return firstCircuit; + } + + private synchronized void descriptorPublished(long now) { + descriptorsPublished++; + if (descriptorsPublished == MIN_DESCRIPTORS_PUBLISHED) + descriptorsPublishedTime = now; + } + + private synchronized void enableNetwork(boolean enable) { + networkEnabled = enable; + circuitBuilt = false; + descriptorsPublished = 0; + descriptorsPublishedTime = Long.MAX_VALUE; + } + + private synchronized boolean isConnected() { + return networkEnabled && bootstrapped && circuitBuilt; + } + + private synchronized boolean shouldPoll(long now) { + return descriptorsPublished < MIN_DESCRIPTORS_PUBLISHED + || now - descriptorsPublishedTime < 2 * pollingInterval; + } + } }