From f6bdbb1b80b67f42c990e0a5e2a0ea44002efc3e Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 14 Oct 2020 11:07:02 +0100 Subject: [PATCH 1/2] Let Tor know if we're on an IPv6-only network. --- .../network/AndroidNetworkManager.java | 98 ++++++++++++++++--- .../bramble/api/network/NetworkStatus.java | 9 +- .../bramble/plugin/tor/TorPlugin.java | 10 +- .../bramble/network/JavaNetworkManager.java | 28 +++--- 4 files changed, 119 insertions(+), 26 deletions(-) diff --git a/bramble-android/src/main/java/org/briarproject/bramble/network/AndroidNetworkManager.java b/bramble-android/src/main/java/org/briarproject/bramble/network/AndroidNetworkManager.java index 3ae99173a..dd6f91018 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/network/AndroidNetworkManager.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/network/AndroidNetworkManager.java @@ -1,11 +1,15 @@ package org.briarproject.bramble.network; +import android.annotation.TargetApi; import android.app.Application; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkInfo; import org.briarproject.bramble.api.event.EventBus; @@ -19,6 +23,11 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler.Cancellable; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -36,16 +45,22 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION; import static android.os.Build.VERSION.SDK_INT; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static java.net.NetworkInterface.getNetworkInterfaces; +import static java.util.Collections.list; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; +import static org.briarproject.bramble.util.LogUtils.logException; @MethodsNotNullByDefault @ParametersNotNullByDefault class AndroidNetworkManager implements NetworkManager, Service { private static final Logger LOG = - Logger.getLogger(AndroidNetworkManager.class.getName()); + getLogger(AndroidNetworkManager.class.getName()); // See android.net.wifi.WifiManager private static final String WIFI_AP_STATE_CHANGED_ACTION = @@ -54,7 +69,8 @@ class AndroidNetworkManager implements NetworkManager, Service { private final TaskScheduler scheduler; private final EventBus eventBus; private final Executor eventExecutor; - private final Context appContext; + private final Application app; + private final ConnectivityManager connectivityManager; private final AtomicReference connectivityCheck = new AtomicReference<>(); private final AtomicBoolean used = new AtomicBoolean(false); @@ -67,7 +83,9 @@ class AndroidNetworkManager implements NetworkManager, Service { this.scheduler = scheduler; this.eventBus = eventBus; this.eventExecutor = eventExecutor; - this.appContext = app.getApplicationContext(); + this.app = app; + connectivityManager = (ConnectivityManager) + requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE)); } @Override @@ -82,24 +100,82 @@ class AndroidNetworkManager implements NetworkManager, Service { filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); - appContext.registerReceiver(networkStateReceiver, filter); + app.registerReceiver(networkStateReceiver, filter); } @Override public void stopService() { if (networkStateReceiver != null) - appContext.unregisterReceiver(networkStateReceiver); + app.unregisterReceiver(networkStateReceiver); } @Override public NetworkStatus getNetworkStatus() { - ConnectivityManager cm = (ConnectivityManager) - appContext.getSystemService(CONNECTIVITY_SERVICE); - if (cm == null) throw new AssertionError(); - NetworkInfo net = cm.getActiveNetworkInfo(); + NetworkInfo net = connectivityManager.getActiveNetworkInfo(); boolean connected = net != null && net.isConnected(); - boolean wifi = connected && net.getType() == TYPE_WIFI; - return new NetworkStatus(connected, wifi); + boolean wifi = false, ipv6Only = false; + if (connected) { + wifi = net.getType() == TYPE_WIFI; + if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only(); + else ipv6Only = areAllAvailableNetworksIpv6Only(); + } + return new NetworkStatus(connected, wifi, ipv6Only); + } + + /** + * Returns true if the + * {@link ConnectivityManager#getActiveNetwork() active network} has an + * IPv6 unicast address and no IPv4 addresses. The active network is + * assumed not to be a loopback interface. + */ + @TargetApi(23) + private boolean isActiveNetworkIpv6Only() { + Network net = connectivityManager.getActiveNetwork(); + if (net == null) { + LOG.info("No active network"); + return false; + } + LinkProperties props = connectivityManager.getLinkProperties(net); + if (props == null) { + LOG.info("No link properties for active network"); + return false; + } + boolean hasIpv6Unicast = false; + for (LinkAddress linkAddress : props.getLinkAddresses()) { + InetAddress addr = linkAddress.getAddress(); + if (addr instanceof Inet4Address) return false; + if (!addr.isMulticastAddress()) hasIpv6Unicast = true; + } + return hasIpv6Unicast; + } + + /** + * Returns true if the device has at least one network interface with an + * IPv6 unicast address and no interfaces with IPv4 addresses, excluding + * loopback interfaces and interfaces that are + * {@link NetworkInterface#isUp() down}. If this method returns true and + * the device has internet access then it's via IPv6 only. + */ + private boolean areAllAvailableNetworksIpv6Only() { + try { + Enumeration interfaces = getNetworkInterfaces(); + if (interfaces == null) { + LOG.info("No network interfaces"); + return false; + } + boolean hasIpv6Unicast = false; + for (NetworkInterface i : list(interfaces)) { + if (i.isLoopback() || !i.isUp()) continue; + for (InetAddress addr : list(i.getInetAddresses())) { + if (addr instanceof Inet4Address) return false; + if (!addr.isMulticastAddress()) hasIpv6Unicast = true; + } + } + return hasIpv6Unicast; + } catch (SocketException e) { + logException(LOG, WARNING, e); + return false; + } } private void updateConnectionStatus() { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/network/NetworkStatus.java b/bramble-api/src/main/java/org/briarproject/bramble/api/network/NetworkStatus.java index 2cdb22e76..348c29f0d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/network/NetworkStatus.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/network/NetworkStatus.java @@ -8,11 +8,12 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class NetworkStatus { - private final boolean connected, wifi; + private final boolean connected, wifi, ipv6Only; - public NetworkStatus(boolean connected, boolean wifi) { + public NetworkStatus(boolean connected, boolean wifi, boolean ipv6Only) { this.connected = connected; this.wifi = wifi; + this.ipv6Only = ipv6Only; } public boolean isConnected() { @@ -22,4 +23,8 @@ public class NetworkStatus { public boolean isWifi() { return wifi; } + + public boolean isIpv6Only() { + return ipv6Only; + } } 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 5fc93f885..d624346e0 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 @@ -863,6 +863,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { if (!state.isTorRunning()) return; boolean online = status.isConnected(); boolean wifi = status.isWifi(); + boolean ipv6Only = status.isIpv6Only(); String country = locationUtils.getCurrentCountry(); boolean blocked = circumventionProvider.isTorProbablyBlocked(country); @@ -879,7 +880,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC; if (LOG.isLoggable(INFO)) { - LOG.info("Online: " + online + ", wifi: " + wifi); + LOG.info("Online: " + online + ", wifi: " + wifi + + ", IPv6 only: " + ipv6Only); if (country.isEmpty()) LOG.info("Country code unknown"); else LOG.info("Country code: " + country); LOG.info("Charging: " + charging); @@ -942,6 +944,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { if (enableNetwork) { enableBridges(enableBridges, useMeek); enableConnectionPadding(enableConnectionPadding); + useIpv6(ipv6Only); } enableNetwork(enableNetwork); } catch (IOException e) { @@ -954,6 +957,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); } + private void useIpv6(boolean ipv6Only) throws IOException { + controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1"); + controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0"); + } + @ThreadSafe @NotNullByDefault protected class PluginState { diff --git a/bramble-java/src/main/java/org/briarproject/bramble/network/JavaNetworkManager.java b/bramble-java/src/main/java/org/briarproject/bramble/network/JavaNetworkManager.java index 75bd3f969..ee46bd11b 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/network/JavaNetworkManager.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/network/JavaNetworkManager.java @@ -5,6 +5,8 @@ import org.briarproject.bramble.api.network.NetworkStatus; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import java.net.Inet4Address; +import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; @@ -14,8 +16,8 @@ import javax.inject.Inject; import static java.net.NetworkInterface.getNetworkInterfaces; import static java.util.Collections.list; -import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logException; @MethodsNotNullByDefault @@ -23,7 +25,7 @@ import static org.briarproject.bramble.util.LogUtils.logException; class JavaNetworkManager implements NetworkManager { private static final Logger LOG = - Logger.getLogger(JavaNetworkManager.class.getName()); + getLogger(JavaNetworkManager.class.getName()); @Inject JavaNetworkManager() { @@ -31,26 +33,28 @@ class JavaNetworkManager implements NetworkManager { @Override public NetworkStatus getNetworkStatus() { - boolean connected = false; + boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false; try { Enumeration interfaces = getNetworkInterfaces(); - if (interfaces != null) { + if (interfaces == null) { + LOG.info("No network interfaces"); + } else { for (NetworkInterface i : list(interfaces)) { - if (i.isLoopback()) continue; - if (i.isUp() && i.getInetAddresses().hasMoreElements()) { - if (LOG.isLoggable(INFO)) { - LOG.info("Interface " + i.getDisplayName() + - " is up with at least one address."); - } + if (i.isLoopback() || !i.isUp()) continue; + for (InetAddress addr : list(i.getInetAddresses())) { connected = true; - break; + if (addr instanceof Inet4Address) { + hasIpv4 = true; + } else if (!addr.isMulticastAddress()) { + hasIpv6Unicast = true; + } } } } } catch (SocketException e) { logException(LOG, WARNING, e); } - return new NetworkStatus(connected, false); + return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast); } } From 02ee678bab3cdeb1ad9cd97b63ebcd53f59cd9d6 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 3 Nov 2020 13:52:12 +0000 Subject: [PATCH 2/2] If using bridges, use meek if the network is IPv6-only. --- .../java/org/briarproject/bramble/plugin/tor/TorPlugin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 d624346e0..17bb73c79 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 @@ -918,7 +918,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { enableNetwork = true; if (network == PREF_TOR_NETWORK_WITH_BRIDGES || (automatic && bridgesWork)) { - if (circumventionProvider.needsMeek(country)) { + if (ipv6Only || + circumventionProvider.needsMeek(country)) { LOG.info("Using meek bridges"); enableBridges = true; useMeek = true;