diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java index c62aac754f3102e7652bcd6414bf6ed7fc0d2d81..2d22e8f4e9706b43b0129aafd374c6cd44a68404 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/AndroidPluginModule.java @@ -48,7 +48,7 @@ public class AndroidPluginModule { appContext, locationUtils, reporter, eventBus, torSocketFactory, backoffFactory); DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, - backoffFactory, appContext); + scheduler, backoffFactory, appContext); Collection<DuplexPluginFactory> duplex = Arrays.asList(bluetooth, tor, lan); @NotNullByDefault diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java index c6eba28c7d8f9508e799fe1861f047b1efa4e09f..506bc997f0dbc8b251cb12ecb038a14b51b39509 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java @@ -20,6 +20,7 @@ import java.net.Socket; import java.net.UnknownHostException; import java.util.Collection; import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -29,16 +30,37 @@ import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.WIFI_SERVICE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE; import static android.os.Build.VERSION.SDK_INT; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.SECONDS; @NotNullByDefault class AndroidLanTcpPlugin extends LanTcpPlugin { + // See android.net.wifi.WifiManager + private static final String WIFI_AP_STATE_CHANGED_ACTION = + "android.net.wifi.WIFI_AP_STATE_CHANGED"; + private static final int WIFI_AP_STATE_ENABLED = 13; + + private static final byte[] WIFI_AP_ADDRESS_BYTES = + {(byte) 192, (byte) 168, 43, 1}; + private static final InetAddress WIFI_AP_ADDRESS; + private static final Logger LOG = Logger.getLogger(AndroidLanTcpPlugin.class.getName()); + static { + try { + WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES); + } catch (UnknownHostException e) { + // Should only be thrown if the address has an illegal length + throw new AssertionError(e); + } + } + + private final ScheduledExecutorService scheduler; private final Context appContext; private final ConnectivityManager connectivityManager; @Nullable @@ -48,10 +70,11 @@ class AndroidLanTcpPlugin extends LanTcpPlugin { private volatile BroadcastReceiver networkStateReceiver = null; private volatile SocketFactory socketFactory; - AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff, - Context appContext, DuplexPluginCallback callback, int maxLatency, - int maxIdleTime) { + AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler, + Backoff backoff, Context appContext, DuplexPluginCallback callback, + int maxLatency, int maxIdleTime) { super(ioExecutor, backoff, callback, maxLatency, maxIdleTime); + this.scheduler = scheduler; this.appContext = appContext; ConnectivityManager connectivityManager = (ConnectivityManager) appContext.getSystemService(CONNECTIVITY_SERVICE); @@ -59,7 +82,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin { this.connectivityManager = connectivityManager; wifiManager = (WifiManager) appContext.getApplicationContext() .getSystemService(WIFI_SERVICE); - socketFactory = getSocketFactory(); + socketFactory = SocketFactory.getDefault(); } @Override @@ -68,7 +91,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin { running = true; // Register to receive network status events networkStateReceiver = new NetworkStateReceiver(); - IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION); + IntentFilter filter = new IntentFilter(); + filter.addAction(CONNECTIVITY_ACTION); + filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); appContext.registerReceiver(networkStateReceiver, filter); } @@ -87,10 +112,17 @@ class AndroidLanTcpPlugin extends LanTcpPlugin { @Override protected Collection<InetAddress> getLocalIpAddresses() { + // If the device doesn't have wifi, don't open any sockets if (wifiManager == null) return emptyList(); + // If we're connected to a wifi network, use that network WifiInfo info = wifiManager.getConnectionInfo(); - if (info == null || info.getIpAddress() == 0) return emptyList(); - return singletonList(intToInetAddress(info.getIpAddress())); + if (info != null && info.getIpAddress() != 0) + return singletonList(intToInetAddress(info.getIpAddress())); + // If we're running an access point, return its address + if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS)) + return singletonList(WIFI_AP_ADDRESS); + // No suitable addresses + return emptyList(); } private InetAddress intToInetAddress(int ip) { @@ -124,9 +156,28 @@ class AndroidLanTcpPlugin extends LanTcpPlugin { @Override public void onReceive(Context ctx, Intent i) { - if (!running || wifiManager == null) return; - WifiInfo info = wifiManager.getConnectionInfo(); - if (info == null || info.getIpAddress() == 0) { + if (!running) return; + if (isApEnabledEvent(i)) { + // The state change may be broadcast before the AP address is + // visible, so delay handling the event + scheduler.schedule(this::handleConnectivityChange, 1, SECONDS); + } else { + handleConnectivityChange(); + } + } + + private void handleConnectivityChange() { + if (!running) return; + Collection<InetAddress> addrs = getLocalIpAddresses(); + if (addrs.contains(WIFI_AP_ADDRESS)) { + LOG.info("Providing wifi hotspot"); + // There's no corresponding Network object and thus no way + // to get a suitable socket factory, so we won't be able to + // make outgoing connections on API 21+ if another network + // has internet access + socketFactory = SocketFactory.getDefault(); + if (socket == null || socket.isClosed()) bind(); + } else if (addrs.isEmpty()) { LOG.info("Not connected to wifi"); socketFactory = SocketFactory.getDefault(); tryToClose(socket); @@ -136,5 +187,10 @@ class AndroidLanTcpPlugin extends LanTcpPlugin { if (socket == null || socket.isClosed()) bind(); } } + + private boolean isApEnabledEvent(Intent i) { + return WIFI_AP_STATE_CHANGED_ACTION.equals(i.getAction()) && + i.getIntExtra(EXTRA_WIFI_STATE, 0) == WIFI_AP_STATE_ENABLED; + } } } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java index 311219a66562ef91edad3a81ee7207f828f42e2f..da8149c9a00ed1d29083734514040f9a43aa41e6 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPluginFactory.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; import javax.annotation.concurrent.Immutable; @@ -27,12 +28,15 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory { private static final double BACKOFF_BASE = 1.2; private final Executor ioExecutor; + private final ScheduledExecutorService scheduler; private final BackoffFactory backoffFactory; private final Context appContext; public AndroidLanTcpPluginFactory(Executor ioExecutor, - BackoffFactory backoffFactory, Context appContext) { + ScheduledExecutorService scheduler, BackoffFactory backoffFactory, + Context appContext) { this.ioExecutor = ioExecutor; + this.scheduler = scheduler; this.backoffFactory = backoffFactory; this.appContext = appContext; } @@ -51,7 +55,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory { public DuplexPlugin createPlugin(DuplexPluginCallback callback) { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext, - callback, MAX_LATENCY, MAX_IDLE_TIME); + return new AndroidLanTcpPlugin(ioExecutor, scheduler, backoff, + appContext, callback, MAX_LATENCY, MAX_IDLE_TIME); } } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index 7ccaa9a2797a4a96b92429996553be31f5f3e252..73b2a39b39657d4ddc4b336c9ca87d1cd69901be 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -16,6 +16,7 @@ import android.os.PowerManager; import net.freehaven.tor.control.EventHandler; import net.freehaven.tor.control.TorControlConnection; +import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.event.Event; @@ -63,8 +64,6 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.ZipInputStream; @@ -111,7 +110,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private static final Logger LOG = Logger.getLogger(TorPlugin.class.getName()); - private final Executor ioExecutor; + private final Executor ioExecutor, connectionStatusExecutor; private final ScheduledExecutorService scheduler; private final Context appContext; private final LocationUtils locationUtils; @@ -125,7 +124,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private final File torDirectory, torFile, geoIpFile, configFile; private final File doneFile, cookieFile; private final PowerManager.WakeLock wakeLock; - private final Lock connectionStatusLock; private final AtomicReference<Future<?>> connectivityCheck = new AtomicReference<>(); private final AtomicBoolean used = new AtomicBoolean(false); @@ -167,7 +165,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { // This tag will prevent Huawei's powermanager from killing us. wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService"); wakeLock.setReferenceCounted(false); - connectionStatusLock = new ReentrantLock(); + // Don't execute more than one connection status check at a time + connectionStatusExecutor = new PoliteExecutor("TorPlugin", + ioExecutor, 1); } @Override @@ -697,54 +697,44 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } private void updateConnectionStatus() { - ioExecutor.execute(() -> { + connectionStatusExecutor.execute(() -> { if (!running) return; - try { - connectionStatusLock.lock(); - updateConnectionStatusLocked(); - } finally { - connectionStatusLock.unlock(); - } - }); - } + 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(); + boolean blocked = TorNetworkMetadata.isTorProbablyBlocked( + country); + Settings s = callback.getSettings(); + int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS); - // Locking: connectionStatusLock - private void updateConnectionStatusLocked() { - 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(); - boolean blocked = TorNetworkMetadata.isTorProbablyBlocked( - country); - Settings s = callback.getSettings(); - int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS); - - if (LOG.isLoggable(INFO)) { - LOG.info("Online: " + online + ", wifi: " + wifi); - if ("".equals(country)) LOG.info("Country code unknown"); - else LOG.info("Country code: " + country); - } + 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"); - enableNetwork(false); - } else if (blocked) { - LOG.info("Disabling network, country is blocked"); - enableNetwork(false); - } else if (network == PREF_TOR_NETWORK_NEVER - || (network == PREF_TOR_NETWORK_WIFI && !wifi)) { - LOG.info("Disabling network due to data setting"); - enableNetwork(false); - } else { - LOG.info("Enabling network"); - enableNetwork(true); + try { + if (!online) { + LOG.info("Disabling network, device is offline"); + enableNetwork(false); + } else if (blocked) { + LOG.info("Disabling network, country is blocked"); + enableNetwork(false); + } else if (network == PREF_TOR_NETWORK_NEVER + || (network == PREF_TOR_NETWORK_WIFI && !wifi)) { + LOG.info("Disabling network due to data setting"); + enableNetwork(false); + } else { + LOG.info("Enabling network"); + enableNetwork(true); + } + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } + }); } private void scheduleConnectionStatusUpdate() { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java index 8b861164df2dd4144bccbea99d0953793e085d8e..78b3cab7c52886d5fef16274b46c3a3d1c1047fc 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.plugin.tcp; +import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; @@ -47,7 +48,7 @@ abstract class TcpPlugin implements DuplexPlugin { private static final Logger LOG = Logger.getLogger(TcpPlugin.class.getName()); - protected final Executor ioExecutor; + protected final Executor ioExecutor, bindExecutor; protected final Backoff backoff; protected final DuplexPluginCallback callback; protected final int maxLatency, maxIdleTime, socketTimeout; @@ -90,6 +91,8 @@ abstract class TcpPlugin implements DuplexPlugin { if (maxIdleTime > Integer.MAX_VALUE / 2) socketTimeout = Integer.MAX_VALUE; else socketTimeout = maxIdleTime * 2; + // Don't execute more than one bind operation at a time + bindExecutor = new PoliteExecutor("TcpPlugin", ioExecutor, 1); } @Override @@ -110,8 +113,9 @@ abstract class TcpPlugin implements DuplexPlugin { } protected void bind() { - ioExecutor.execute(() -> { + bindExecutor.execute(() -> { if (!running) return; + if (socket != null && !socket.isClosed()) return; ServerSocket ss = null; for (InetSocketAddress addr : getLocalSocketAddresses()) { try {