diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java index 944399799..f4a7b181f 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java @@ -8,33 +8,64 @@ import java.util.List; public interface CircumventionProvider { + enum BridgeType { + DEFAULT_OBFS4, + NON_DEFAULT_OBFS4, + MEEK + } + /** * Countries where Tor is blocked, i.e. vanilla Tor connection won't work. - * + *

* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 * and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki */ - String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"}; + String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"}; /** * Countries where obfs4 or meek bridge connections are likely to work. - * Should be a subset of {@link #BLOCKED}. + * Should be a subset of {@link #BLOCKED} and the union of + * {@link #DEFAULT_OBFS4_BRIDGES}, {@link #NON_DEFAULT_OBFS4_BRIDGES} and + * {@link #MEEK_BRIDGES}. */ - String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" }; + String[] BRIDGES = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"}; + + /** + * Countries where default obfs4 bridges are likely to work. + * Should be a subset of {@link #BRIDGES}. + */ + String[] DEFAULT_OBFS4_BRIDGES = {"EG", "BY", "TR", "SY", "VE"}; + + /** + * Countries where non-default obfs4 bridges are likely to work. + * Should be a subset of {@link #BRIDGES}. + */ + String[] NON_DEFAULT_OBFS4_BRIDGES = {"RU"}; /** * Countries where obfs4 bridges won't work and meek is needed. * Should be a subset of {@link #BRIDGES}. */ - String[] NEEDS_MEEK = {"CN", "IR"}; + String[] MEEK_BRIDGES = {"CN", "IR"}; + /** + * Returns true if vanilla Tor connections are blocked in the given country. + */ boolean isTorProbablyBlocked(String countryCode); + /** + * Returns true if bridge connections of some type work in the given + * country. + */ boolean doBridgesWork(String countryCode); - boolean needsMeek(String countryCode); + /** + * Returns the best type of bridge connection for the given country, or + * {@link #DEFAULT_OBFS4_BRIDGES} if no bridge type is known to work. + */ + BridgeType getBestBridgeType(String countryCode); @IoExecutor - List getBridges(boolean meek); + List getBridges(BridgeType type); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java index 2e8c8ad90..2874c9823 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java @@ -9,10 +9,13 @@ import java.util.List; import java.util.Scanner; import java.util.Set; -import javax.annotation.Nullable; import javax.inject.Inject; import static java.util.Arrays.asList; +import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; +import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4; +import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; +import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4; class CircumventionProviderImpl implements CircumventionProvider { @@ -20,13 +23,14 @@ class CircumventionProviderImpl implements CircumventionProvider { private static final Set BLOCKED_IN_COUNTRIES = new HashSet<>(asList(BLOCKED)); - private static final Set BRIDGES_WORK_IN_COUNTRIES = + private static final Set BRIDGE_COUNTRIES = new HashSet<>(asList(BRIDGES)); - private static final Set BRIDGES_NEED_MEEK = - new HashSet<>(asList(NEEDS_MEEK)); - - @Nullable - private volatile List bridges = null; + private static final Set DEFAULT_OBFS4_BRIDGE_COUNTRIES = + new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES)); + private static final Set NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES = + new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES)); + private static final Set MEEK_COUNTRIES = + new HashSet<>(asList(MEEK_BRIDGES)); @Inject CircumventionProviderImpl() { @@ -39,33 +43,42 @@ class CircumventionProviderImpl implements CircumventionProvider { @Override public boolean doBridgesWork(String countryCode) { - return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode); + return BRIDGE_COUNTRIES.contains(countryCode); } @Override - public boolean needsMeek(String countryCode) { - return BRIDGES_NEED_MEEK.contains(countryCode); + public BridgeType getBestBridgeType(String countryCode) { + if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) { + return DEFAULT_OBFS4; + } else if (NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) { + return NON_DEFAULT_OBFS4; + } else if (MEEK_COUNTRIES.contains(countryCode)) { + return MEEK; + } else { + return DEFAULT_OBFS4; + } } @Override @IoExecutor - public List getBridges(boolean useMeek) { - List bridges = this.bridges; - if (bridges != null) return new ArrayList<>(bridges); - - InputStream is = getClass().getClassLoader() - .getResourceAsStream(BRIDGE_FILE_NAME); + public List getBridges(BridgeType type) { + InputStream is = requireNonNull(getClass().getClassLoader() + .getResourceAsStream(BRIDGE_FILE_NAME)); Scanner scanner = new Scanner(is); - bridges = new ArrayList<>(); + List bridges = new ArrayList<>(); while (scanner.hasNextLine()) { String line = scanner.nextLine(); - boolean isMeekBridge = line.startsWith("Bridge meek"); - if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue; - if (!line.startsWith("#")) bridges.add(line); + boolean isDefaultObfs4 = line.startsWith("d "); + boolean isNonDefaultObfs4 = line.startsWith("n "); + boolean isMeek = line.startsWith("m "); + if ((type == DEFAULT_OBFS4 && isDefaultObfs4) || + (type == NON_DEFAULT_OBFS4 && isNonDefaultObfs4) || + (type == MEEK && isMeek)) { + bridges.add(line.substring(2)); + } } scanner.close(); - this.bridges = bridges; return bridges; } 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 35d6548eb..f443b01bd 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 @@ -33,6 +33,7 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.ResourceProvider; +import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType; import java.io.ByteArrayInputStream; import java.io.EOFException; @@ -92,6 +93,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA; +import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES; import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.tryToClose; @@ -542,20 +544,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); } - private void enableBridges(boolean enable, boolean needsMeek) + private void enableBridges(boolean enable, BridgeType bridgeType) throws IOException { if (enable) { Collection conf = new ArrayList<>(); conf.add("UseBridges 1"); File obfs4File = getObfs4ExecutableFile(); - if (needsMeek) { + if (bridgeType == MEEK) { conf.add("ClientTransportPlugin meek_lite exec " + obfs4File.getAbsolutePath()); } else { conf.add("ClientTransportPlugin obfs4 exec " + obfs4File.getAbsolutePath()); } - conf.addAll(circumventionProvider.getBridges(needsMeek)); + conf.addAll(circumventionProvider.getBridges(bridgeType)); controlConnection.setConf(conf); } else { controlConnection.setConf("UseBridges", "0"); @@ -844,7 +846,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { int reasonsDisabled = 0; boolean enableNetwork = false, enableBridges = false; - boolean useMeek = false, enableConnectionPadding = false; + boolean enableConnectionPadding = false; + BridgeType bridgeType = + circumventionProvider.getBestBridgeType(country); if (!online) { LOG.info("Disabling network, device is offline"); @@ -873,14 +877,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { enableNetwork = true; if (network == PREF_TOR_NETWORK_WITH_BRIDGES || (automatic && bridgesWork)) { - if (ipv6Only || - circumventionProvider.needsMeek(country)) { - LOG.info("Using meek bridges"); - enableBridges = true; - useMeek = true; - } else { - LOG.info("Using obfs4 bridges"); - enableBridges = true; + if (ipv6Only) bridgeType = MEEK; + enableBridges = true; + if (LOG.isLoggable(INFO)) { + LOG.info("Using bridge type " + bridgeType); } } else { LOG.info("Not using bridges"); @@ -898,7 +898,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { try { if (enableNetwork) { - enableBridges(enableBridges, useMeek); + enableBridges(enableBridges, bridgeType); enableConnectionPadding(enableConnectionPadding); useIpv6(ipv6Only); } diff --git a/bramble-core/src/main/resources/bridges b/bramble-core/src/main/resources/bridges index 73188eba1..e18f25969 100644 --- a/bramble-core/src/main/resources/bridges +++ b/bramble-core/src/main/resources/bridges @@ -1,10 +1,15 @@ -Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0 -Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0 -Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0 -Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0 -Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0 -Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0 -Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0 -Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0 -Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0 -Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com \ No newline at end of file +d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0 +d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0 +d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0 +d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0 +d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0 +d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0 +d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0 +d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0 +d Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0 +n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0 +n Bridge obfs4 68.190.6.146:40405 165F8A3EFC2230DFB8EAAB3A1000842C6F9EA724 cert=UgE40NEarWVh+ev5INzxDV/WW1ghgYDvytxtSIeNL5U6D9Vng7vE5BbI7LgOu2Oiu4PdBA iat-mode=0 +n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0 +n Bridge obfs4 23.88.49.56:443 1CDA1660823AE2565D7F50DE8EB99DFDDE96074B cert=4bwNXedHutVD0ZqCm6ph90Vik9dRY4n9qnBHiLiqQOSsIvui4iHwuMFQK6oqiK8tyhVcDw iat-mode=0 +n Bridge obfs4 185.65.206.101:443 8A3E001D4C5105ED41060597DEEB21FF19CDC4D3 cert=Nd6XZ+f00sGKL1u6USmyvfqR34HN/pt7jEVbgMpXPF/yyGaLBiXRH/x0SIjX5TceYnd+Dg iat-mode=0 +m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com \ No newline at end of file diff --git a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java index 04031f8c3..c3b6d41a5 100644 --- a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java +++ b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java @@ -39,6 +39,8 @@ import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT; +import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4; +import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled; @@ -57,10 +59,14 @@ public class BridgeTest extends BrambleTestCase { .injectEagerSingletons(component); // Share a failure counter among all the test instances AtomicInteger failures = new AtomicInteger(0); - List bridges = - component.getCircumventionProvider().getBridges(false); - List states = new ArrayList<>(bridges.size()); - for (String bridge : bridges) states.add(new Params(bridge, failures)); + CircumventionProvider provider = component.getCircumventionProvider(); + List states = new ArrayList<>(); + for (String bridge : provider.getBridges(DEFAULT_OBFS4)) { + states.add(new Params(bridge, true, failures)); + } + for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) { + states.add(new Params(bridge, false, failures)); + } return states; } @@ -94,12 +100,14 @@ public class BridgeTest extends BrambleTestCase { private final File torDir = getTestDirectory(); private final String bridge; + private final boolean isDefault; private final AtomicInteger failures; private UnixTorPluginFactory factory; public BridgeTest(Params params) { bridge = params.bridge; + isDefault = params.isDefault; failures = params.failures; } @@ -132,12 +140,12 @@ public class BridgeTest extends BrambleTestCase { } @Override - public boolean needsMeek(String countryCode) { - return false; + public BridgeType getBestBridgeType(String countryCode) { + return isDefault ? DEFAULT_OBFS4 : NON_DEFAULT_OBFS4; } @Override - public List getBridges(boolean useMeek) { + public List getBridges(BridgeType bridgeType) { return singletonList(bridge); } }; @@ -182,10 +190,13 @@ public class BridgeTest extends BrambleTestCase { private static class Params { private final String bridge; + private final boolean isDefault; private final AtomicInteger failures; - private Params(String bridge, AtomicInteger failures) { + private Params(String bridge, boolean isDefault, + AtomicInteger failures) { this.bridge = bridge; + this.isDefault = isDefault; this.failures = failures; } }