diff options
author | cyberta <cyberta@riseup.net> | 2022-07-19 17:26:25 +0000 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2022-07-19 17:26:25 +0000 |
commit | 6bc3a79e6d09a8fa1857f1d502a87c1c0633be35 (patch) | |
tree | 7aa914e63bb6aae27c8521dd198e3f7416e9890c /app/src/main | |
parent | abd8c8d06f01cc5b794a18779c2ea36b9317b4e3 (diff) | |
parent | 4a95fbad79333bc1cc83b22c8122293a3d8d332f (diff) |
Merge branch 'integrate_obfsvpn' into 'master'
update bitmaskcore, integrating obfsvpn
See merge request leap/bitmask_android!190
Diffstat (limited to 'app/src/main')
22 files changed, 552 insertions, 175 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index c010ef54..7dd75432 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -70,6 +70,8 @@ import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_KCP; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; import static se.leap.bitmaskclient.base.utils.ConfigHelper.stringEqual; @@ -191,7 +193,8 @@ public class VpnProfile implements Serializable, Cloneable { private int mProfileVersion; public boolean mBlockUnusedAddressFamilies = true; public String mGatewayIp; - public boolean mUsePluggableTransports; + private boolean mUseObfs4; + private boolean mUseObfs4Kcp; public VpnProfile(String name, Connection.TransportType transportType) { mUuid = UUID.randomUUID(); @@ -200,7 +203,8 @@ public class VpnProfile implements Serializable, Cloneable { mConnections = new Connection[1]; mLastUsed = System.currentTimeMillis(); - mUsePluggableTransports = transportType == OBFS4; + mUseObfs4 = transportType == OBFS4; + mUseObfs4Kcp = transportType == OBFS4_KCP; } public static String openVpnEscape(String unescaped) { @@ -266,7 +270,8 @@ public class VpnProfile implements Serializable, Cloneable { if (obj instanceof VpnProfile) { VpnProfile vp = (VpnProfile) obj; return stringEqual(vp.mGatewayIp, mGatewayIp) && - vp.mUsePluggableTransports == mUsePluggableTransports; + vp.mUseObfs4 == mUseObfs4 && + vp.mUseObfs4Kcp == mUseObfs4Kcp; } return false; } @@ -296,6 +301,20 @@ public class VpnProfile implements Serializable, Cloneable { mUuid = uuid; } + public boolean usePluggableTransports() { + return mUseObfs4Kcp || mUseObfs4; + } + + public Connection.TransportType getTransportType() { + if (mUseObfs4) { + return OBFS4; + } else if (mUseObfs4Kcp) { + return OBFS4_KCP; + } else { + return OPENVPN; + } + } + public String getName() { if (TextUtils.isEmpty(mName)) return "No profile name"; @@ -478,7 +497,7 @@ public class VpnProfile implements Serializable, Cloneable { cfg.append(insertFileData("crl-verify", mCrlFilename)); // compression does not work in conjunction with shapeshifter-dispatcher so far - if (mUseLzo && !mUsePluggableTransports) { + if (mUseLzo && !usePluggableTransports()) { cfg.append("comp-lzo\n"); } diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index d624af80..4b394136 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -5,6 +5,12 @@ package de.blinkt.openvpn.core; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; +import static de.blinkt.openvpn.core.NetworkSpace.IpAddress; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn; + import android.Manifest.permission; import android.annotation.TargetApi; import android.app.Notification; @@ -47,13 +53,9 @@ import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VpnNotificationManager; import se.leap.bitmaskclient.firewall.FirewallManager; +import se.leap.bitmaskclient.pluggableTransports.ObfsVpnClient; import se.leap.bitmaskclient.pluggableTransports.Shapeshifter; -import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED; -import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; -import static de.blinkt.openvpn.core.NetworkSpace.IpAddress; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; - public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal, VpnNotificationManager.VpnServiceCallback { public static final String TAG = OpenVPNService.class.getSimpleName(); @@ -89,6 +91,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private Runnable mOpenVPNThread; private VpnNotificationManager notificationManager; private Shapeshifter shapeshifter; + private ObfsVpnClient obfsVpnClient; private FirewallManager firewallManager; private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() { @@ -241,6 +244,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (shapeshifter != null) { shapeshifter.stop(); shapeshifter = null; + } else if (obfsVpnClient != null && obfsVpnClient.isStarted()) { + obfsVpnClient.stop(); + obfsVpnClient = null; } VpnStatus.updateStateString("NOPROCESS", "VPN STOPPED", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); } @@ -311,7 +317,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, ConnectionStatus.LEVEL_START); notificationManager.buildOpenVpnNotification( mProfile != null ? mProfile.mName : "", - mProfile != null && mProfile.mUsePluggableTransports, + mProfile != null && mProfile.usePluggableTransports(), VpnStatus.getLastCleanLogMessage(this), VpnStatus.getLastCleanLogMessage(this), ConnectionStatus.LEVEL_START, @@ -410,9 +416,16 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // An old running VPN should now be exited mStarting = false; - if (mProfile.mUsePluggableTransports && connection instanceof Obfs4Connection) { + if (mProfile.usePluggableTransports() && connection instanceof Obfs4Connection) { Obfs4Connection obfs4Connection = (Obfs4Connection) connection; - if (shapeshifter == null) { + if (useObfsVpn()) { + if (obfsVpnClient != null && obfsVpnClient.isStarted()) { + obfsVpnClient.stop(); + } + obfsVpnClient = new ObfsVpnClient(obfs4Connection.getDispatcherOptions()); + int runningSocksPort = obfsVpnClient.start(); + connection.setProxyPort(String.valueOf(runningSocksPort)); + } else if (shapeshifter == null) { shapeshifter = new Shapeshifter(obfs4Connection.getDispatcherOptions()); shapeshifter.start(); } @@ -474,6 +487,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac Log.d(TAG, "-> stop shapeshifter"); shapeshifter.stop(); shapeshifter = null; + } else if (obfsVpnClient != null && obfsVpnClient.isStarted()) { + Log.d(TAG, "-> stop obfsvpnClient"); + obfsVpnClient.stop(); + obfsVpnClient = null; } try { Thread.sleep(1000); @@ -1016,7 +1033,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac notificationManager.buildOpenVpnNotification( mProfile != null ? mProfile.mName : "", - mProfile != null && mProfile.mUsePluggableTransports, + mProfile != null && mProfile.usePluggableTransports(), VpnStatus.getLastCleanLogMessage(this), VpnStatus.getLastCleanLogMessage(this), level, @@ -1047,7 +1064,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, getResources())); notificationManager.buildOpenVpnNotification( mProfile != null ? mProfile.mName : "", - mProfile != null && mProfile.mUsePluggableTransports, + mProfile != null && mProfile.usePluggableTransports(), netstat, null, LEVEL_CONNECTED, @@ -1091,7 +1108,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.updateStateString("NEED", "need " + needed, resid, LEVEL_WAITING_FOR_USER_INPUT); notificationManager.buildOpenVpnNotification( mProfile != null ? mProfile.mName : "", - mProfile != null && mProfile.mUsePluggableTransports, + mProfile != null && mProfile.usePluggableTransports(), getString(resid), getString(resid), LEVEL_WAITING_FOR_USER_INPUT, diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index 4fa5a7b6..f28651f3 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -543,6 +543,6 @@ public class VpnStatus { } public static boolean isUsingBridges() { - return lastConnectedProfile != null && lastConnectedProfile.mUsePluggableTransports; + return lastConnectedProfile != null && lastConnectedProfile.usePluggableTransports(); } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java b/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java index 4cb9c0c7..f60e7333 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java @@ -39,7 +39,10 @@ public abstract class Connection implements Serializable, Cloneable { public enum TransportType { OBFS4("obfs4"), - OPENVPN("openvpn"); + OPENVPN("openvpn"), + OBFS4_KCP("obfs4-1"), + + PT("metaTransport"); String transport; @@ -51,6 +54,17 @@ public abstract class Connection implements Serializable, Cloneable { public String toString() { return transport; } + + public boolean isPluggableTransport() { + return this == OBFS4 || this == OBFS4_KCP || this == PT; + } + + public TransportType getMetaType() { + if (this == OBFS4 || this == OBFS4_KCP || this == PT) { + return PT; + } + return OPENVPN; + } } diff --git a/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java b/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java index 82a7a6aa..5189afcc 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java +++ b/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java @@ -1,10 +1,12 @@ package de.blinkt.openvpn.core.connection; -import se.leap.bitmaskclient.pluggableTransports.Obfs4Options; - +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn; import static se.leap.bitmaskclient.pluggableTransports.Shapeshifter.DISPATCHER_IP; import static se.leap.bitmaskclient.pluggableTransports.Shapeshifter.DISPATCHER_PORT; +import se.leap.bitmaskclient.pluggableTransports.Obfs4Options; +import se.leap.bitmaskclient.pluggableTransports.ObfsVpnClient; + /** * Created by cyberta on 08.03.19. @@ -16,14 +18,24 @@ public class Obfs4Connection extends Connection { private Obfs4Options options; public Obfs4Connection(Obfs4Options options) { + if (useObfsVpn()) { + setServerName(options.remoteIP); + setServerPort(options.remotePort); + setProxyName(ObfsVpnClient.SOCKS_IP); + setProxyPort(String.valueOf(ObfsVpnClient.SOCKS_PORT.get())); + setProxyType(ProxyType.SOCKS5); + } else { + setServerName(DISPATCHER_IP); + setServerPort(DISPATCHER_PORT); + setProxyName(""); + setProxyPort(""); + setProxyType(ProxyType.NONE); + } + // while udp/kcp might be used on the wire, + // we don't use udp for openvpn in case of a obfs4 connection setUseUdp(false); - setServerName(DISPATCHER_IP); - setServerPort(DISPATCHER_PORT); - setProxyName(""); - setProxyPort(""); setProxyAuthUser(null); setProxyAuthPassword(null); - setProxyType(ProxyType.NONE); setUseProxyAuth(false); this.options = options; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java index f2c3b2d6..a2bfff7c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java @@ -51,10 +51,10 @@ import se.leap.bitmaskclient.eip.GatewaysManager; import static android.content.Context.MODE_PRIVATE; import static android.view.View.GONE; -import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT; import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES; @@ -92,7 +92,7 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca eipStatus = EipStatus.getInstance(); eipStatus.addObserver(this); preferences = getContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - selectedTransport = getUseBridges(preferences) ? OBFS4 : OPENVPN; + selectedTransport = getUseBridges(preferences) ? PT : OPENVPN; preferences.registerOnSharedPreferenceChangeListener(this); } @@ -211,7 +211,7 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key.equals(USE_BRIDGES)) { boolean showBridges = getUseBridges(sharedPreferences); - selectedTransport = showBridges ? OBFS4 : OPENVPN; + selectedTransport = showBridges ? PT : OPENVPN; gatewaysManager.updateTransport(selectedTransport); locationListAdapter.updateTransport(selectedTransport, gatewaysManager); bridgesHint.setVisibility(showBridges ? VISIBLE : GONE); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java index 1c859d65..c5593bf7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -32,7 +32,6 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AlertDialog; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java index f4531ff8..94f737b4 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java @@ -9,12 +9,15 @@ import static se.leap.bitmaskclient.base.models.Constants.PREFER_UDP; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES; import static se.leap.bitmaskclient.base.models.Constants.USE_IPv6_FIREWALL; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.allowExperimentalTransports; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferUDP; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getShowAlwaysOnDialog; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseBridges; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseSnowflake; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.hasSnowflakePrefs; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.preferUDP; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setAllowExperimentalTransports; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useBridges; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useSnowflake; import static se.leap.bitmaskclient.base.utils.ViewHelper.setActionBarTitle; @@ -81,6 +84,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh initFirewallEntry(view); initTetheringEntry(view); initGatewayPinningEntry(view); + initExperimentalTransportsEntry(view); setActionBarTitle(this, advanced_settings); return view; } @@ -249,6 +253,23 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh }); } + public void initExperimentalTransportsEntry(View rootView) { + IconSwitchEntry experimentalTransports = rootView.findViewById(R.id.experimental_transports); + if (useObfsVpn() && ProviderObservable.getInstance().getCurrentProvider().supportsExperimentalPluggableTransports()) { + experimentalTransports.setVisibility(VISIBLE); + experimentalTransports.setChecked(allowExperimentalTransports(this.getContext())); + experimentalTransports.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (!buttonView.isPressed()) { + return; + } + setAllowExperimentalTransports(getContext(), isChecked); + }); + } else { + experimentalTransports.setVisibility(GONE); + } + + } + public void showTetheringAlert() { try { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index bde909ba..b34a31eb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -44,6 +44,7 @@ public interface Constants { String USE_SNOWFLAKE = "use_snowflake"; String PREFER_UDP = "prefer_UDP"; String GATEWAY_PINNING = "gateway_pinning"; + String ALLOW_EXPERIMENTAL_TRANSPORTS = "allow_experimental_transports"; ////////////////////////////////////////////// @@ -163,6 +164,7 @@ public interface Constants { String IP_ADDRESS = "ip_address"; String IP_ADDRESS6 = "ip_address6"; String REMOTE = "remote"; + String SOCKS_PROXY = "socks-proxy"; String PORTS = "ports"; String PROTOCOLS = "protocols"; String UDP = "udp"; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java index 064f25c0..26f6b14a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java @@ -21,10 +21,7 @@ import androidx.annotation.NonNull; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; -import java.util.function.ToDoubleFunction; -import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Connection.TransportType; public class Location implements Cloneable { @@ -50,27 +47,27 @@ public class Location implements Cloneable { } public boolean supportsTransport(TransportType transportType) { - return numberOfGateways.containsKey(transportType); + return numberOfGateways.containsKey(transportType.getMetaType()); } public void setAverageLoad(TransportType transportType, double load) { - averageLoad.put(transportType, load); + averageLoad.put(transportType.getMetaType(), load); } public double getAverageLoad(TransportType transportType) { - if (averageLoad.containsKey(transportType)) { - return averageLoad.get(transportType); + if (averageLoad.containsKey(transportType.getMetaType())) { + return averageLoad.get(transportType.getMetaType()); } return 0; } public void setNumberOfGateways(TransportType transportType, int numbers) { - numberOfGateways.put(transportType, numbers); + numberOfGateways.put(transportType.getMetaType(), numbers); } public int getNumberOfGateways(TransportType transportType) { - if (numberOfGateways.containsKey(transportType)) { - return numberOfGateways.get(transportType); + if (numberOfGateways.containsKey(transportType.getMetaType())) { + return numberOfGateways.get(transportType.getMetaType()); } return 0; } @@ -112,7 +109,7 @@ public class Location implements Cloneable { public static class SortByAverageLoad implements Comparator<Location> { TransportType transportType; public SortByAverageLoad(TransportType transportType) { - this.transportType = transportType; + this.transportType = transportType.getMetaType(); } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Pair.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Pair.java new file mode 100644 index 00000000..e2ef4622 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Pair.java @@ -0,0 +1,57 @@ +package se.leap.bitmaskclient.base.models; + +import java.util.Objects; + +/** + * Container to ease passing around a tuple of two objects. This object provides a sensible + * implementation of equals(), returning true if equals() is true on each of the contained + * objects. + */ +public class Pair<F, S> { + public final F first; + public final S second; + + /** + * Constructor for a Pair. + * + * @param first the first object in the Pair + * @param second the second object in the pair + */ + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + /** + * Checks the two objects for equality by delegating to their respective + * {@link Object#equals(Object)} methods. + * + * @param o the {@link Pair} to which this one is to be checked for equality + * @return true if the underlying objects of the Pair are both considered + * equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + Pair<?, ?> p = (Pair<?, ?>) o; + return Objects.equals(p.first, first) && Objects.equals(p.second, second); + } + + /** + * Compute a hash code using the hash codes of the underlying objects + * + * @return a hashcode of the Pair + */ + @Override + public int hashCode() { + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + } + + @Override + public String toString() { + return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}"; + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java index 02a9694a..7b8f22af 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java @@ -30,6 +30,7 @@ import java.net.URL; import java.util.Locale; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_KCP; import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS; import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; @@ -37,8 +38,12 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOWED_REGIS import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOW_ANONYMOUS; import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; import static se.leap.bitmaskclient.base.models.Constants.TYPE; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; +import de.blinkt.openvpn.core.connection.Connection; +import de.blinkt.openvpn.core.connection.Connection.TransportType; + /** * @author Sean Leonard <meanderingcode@aetherislands.net> * @author Parménides GV <parmegv@sdf.org> @@ -161,6 +166,17 @@ public final class Provider implements Parcelable { } public boolean supportsPluggableTransports() { + if (useObfsVpn()) { + return supportsTransports(new TransportType[]{OBFS4, OBFS4_KCP}); + } + return supportsTransports(new TransportType[]{OBFS4}); + } + + public boolean supportsExperimentalPluggableTransports() { + return supportsTransports(new TransportType[]{OBFS4_KCP}); + } + + private boolean supportsTransports(TransportType[] transportTypes) { try { JSONArray gatewayJsons = eipServiceJson.getJSONArray(GATEWAYS); for (int i = 0; i < gatewayJsons.length(); i++) { @@ -168,15 +184,18 @@ public final class Provider implements Parcelable { getJSONObject(CAPABILITIES). getJSONArray(TRANSPORT); for (int j = 0; j < transports.length(); j++) { - if (OBFS4.toString().equals(transports.getJSONObject(j).getString(TYPE))) { - return true; + String supportedTransportType = transports.getJSONObject(j).getString(TYPE); + for (TransportType transportType : transportTypes) { + if (transportType.toString().equals(supportedTransportType)) { + return true; + } } } } } catch (Exception e) { - // ignore + // ignore } - return false; + return false; } public String getIpForHostname(String host) { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java index ca1261a8..8ac5baf0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java @@ -274,4 +274,11 @@ public class ConfigHelper { return matcher.matches(); } + // ObfsVpnHelper class allows us to mock BuildConfig.use_obfsvpn while + // not mocking the whole ConfigHelper class + public static class ObfsVpnHelper { + public static boolean useObfsVpn() { + return BuildConfig.use_obfsvpn; + } + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java index 08bfbdc3..3a2cf754 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java @@ -1,25 +1,7 @@ package se.leap.bitmaskclient.base.utils; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.Preference; - -import androidx.annotation.NonNull; -import androidx.annotation.WorkerThread; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashSet; -import java.util.Set; - -import de.blinkt.openvpn.VpnProfile; -import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.tor.TorStatusObservable; - import static android.content.Context.MODE_PRIVATE; +import static se.leap.bitmaskclient.base.models.Constants.ALLOW_EXPERIMENTAL_TRANSPORTS; import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_BLUETOOTH; import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_USB; import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_WIFI; @@ -42,6 +24,24 @@ import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES; import static se.leap.bitmaskclient.base.models.Constants.USE_IPv6_FIREWALL; import static se.leap.bitmaskclient.base.models.Constants.USE_SNOWFLAKE; +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +import de.blinkt.openvpn.VpnProfile; +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.tor.TorStatusObservable; + /** * Created by cyberta on 18.03.18. */ @@ -238,6 +238,14 @@ public class PreferenceHelper { return getBoolean(context, SHOW_EXPERIMENTAL, false); } + public static void setAllowExperimentalTransports(Context context, boolean show) { + putBoolean(context, ALLOW_EXPERIMENTAL_TRANSPORTS, show); + } + + public static boolean allowExperimentalTransports(Context context) { + return getBoolean(context, ALLOW_EXPERIMENTAL_TRANSPORTS, false); + } + public static void setUseIPv6Firewall(Context context, boolean useFirewall) { putBoolean(context, USE_IPv6_FIREWALL, useFirewall); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/SelectLocationEntry.java b/app/src/main/java/se/leap/bitmaskclient/base/views/SelectLocationEntry.java index 3d4f93ff..554fe958 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/views/SelectLocationEntry.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/SelectLocationEntry.java @@ -6,7 +6,6 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatTextView; @@ -71,7 +70,7 @@ public class SelectLocationEntry extends LinearLayout { boolean supportsSelectedTransport = location.supportsTransport(transportType); locationText.setVisibility(hasData ? VISIBLE : GONE); locationIndicator.setVisibility(hasData ? VISIBLE : GONE); - bridgesView.setVisibility(transportType == OBFS4 && supportsSelectedTransport ? VISIBLE : GONE); + bridgesView.setVisibility(transportType.isPluggableTransport() && supportsSelectedTransport ? VISIBLE : GONE); locationText.setText(location.getName()); locationIndicator.setLoad(Load.getLoadByValue(location.getAverageLoad(transportType))); selectedView.setChecked(location.selected); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 46f91781..b3efd21f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -16,6 +16,36 @@ */ package se.leap.bitmaskclient.eip; +import static android.app.Activity.RESULT_CANCELED; +import static android.app.Activity.RESULT_OK; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; +import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_RECEIVER; +import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.NO_MORE_GATEWAYS; +import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; + import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Context; @@ -31,6 +61,7 @@ import android.os.ResultReceiver; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.WorkerThread; import androidx.core.app.JobIntentService; @@ -57,42 +88,10 @@ import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.OnBootReceiver; import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.models.Pair; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import static android.app.Activity.RESULT_CANCELED; -import static android.app.Activity.RESULT_OK; -import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; -import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; -import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; -import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; -import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; -import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; -import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN; -import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; -import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_RECEIVER; -import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseBridges; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.NO_MORE_GATEWAYS; -import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; - /** * EIP is the abstract base class for interacting with and managing the Encrypted * Internet Proxy connection. Connections are started, stopped, and queried through @@ -256,8 +255,8 @@ public final class EIP extends JobIntentService implements Observer { return; } - Gateway gateway = gatewaysManager.select(nClosestGateway); - launchActiveGateway(gateway, nClosestGateway, result); + Pair<Gateway, Connection.TransportType> gatewayTransportTypePair = gatewaysManager.select(nClosestGateway); + launchActiveGateway(gatewayTransportTypePair, nClosestGateway, result); if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) { tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result); } else { @@ -271,7 +270,7 @@ public final class EIP extends JobIntentService implements Observer { */ private void startEIPAlwaysOnVpn() { GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext()); - Gateway gateway = gatewaysManager.select(0); + Pair<Gateway, Connection.TransportType> gatewayTransportTypePair = gatewaysManager.select(0); Bundle result = new Bundle(); if (shouldUpdateVPNCertificate()) { @@ -280,7 +279,7 @@ public final class EIP extends JobIntentService implements Observer { ProviderObservable.getInstance().updateProvider(p); } - launchActiveGateway(gateway, 0, result); + launchActiveGateway(gatewayTransportTypePair, 0, result); if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){ VpnStatus.logWarning("ALWAYS-ON VPN: " + getString(R.string.no_vpn_profiles_defined)); } @@ -324,13 +323,13 @@ public final class EIP extends JobIntentService implements Observer { /** * starts the VPN and connects to the given gateway * - * @param gateway to connect to + * @param gatewayTransportTypePair Pair of Gateway and associated transport used to connect */ - private void launchActiveGateway(Gateway gateway, int nClosestGateway, Bundle result) { + private void launchActiveGateway(@Nullable Pair<Gateway, Connection.TransportType> gatewayTransportTypePair, int nClosestGateway, Bundle result) { VpnProfile profile; - Connection.TransportType transportType = getUseBridges(this) ? OBFS4 : OPENVPN; - if (gateway == null || - (profile = gateway.getProfile(transportType)) == null) { + + if (gatewayTransportTypePair == null || gatewayTransportTypePair.first == null || + (profile = gatewayTransportTypePair.first.getProfile(gatewayTransportTypePair.second)) == null) { String preferredLocation = getPreferredCity(getApplicationContext()); if (preferredLocation != null) { setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name), preferredLocation); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 60507363..e9281609 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -16,6 +16,17 @@ */ package se.leap.bitmaskclient.eip; +import static se.leap.bitmaskclient.base.models.Constants.FULLNESS; +import static se.leap.bitmaskclient.base.models.Constants.HOST; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; +import static se.leap.bitmaskclient.base.models.Constants.LOCATION; +import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; +import static se.leap.bitmaskclient.base.models.Constants.NAME; +import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION; +import static se.leap.bitmaskclient.base.models.Constants.OVERLOAD; +import static se.leap.bitmaskclient.base.models.Constants.TIMEZONE; +import static se.leap.bitmaskclient.base.models.Constants.VERSION; + import android.content.Context; import androidx.annotation.NonNull; @@ -36,17 +47,6 @@ import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import static se.leap.bitmaskclient.base.models.Constants.FULLNESS; -import static se.leap.bitmaskclient.base.models.Constants.HOST; -import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; -import static se.leap.bitmaskclient.base.models.Constants.LOCATION; -import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; -import static se.leap.bitmaskclient.base.models.Constants.NAME; -import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION; -import static se.leap.bitmaskclient.base.models.Constants.OVERLOAD; -import static se.leap.bitmaskclient.base.models.Constants.TIMEZONE; -import static se.leap.bitmaskclient.base.models.Constants.VERSION; - /** * Gateway provides objects defining gateways and their metadata. * Each instance contains a VpnProfile for OpenVPN specific data and member @@ -175,7 +175,8 @@ public class Gateway { private @NonNull HashMap<Connection.TransportType, VpnProfile> createVPNProfiles(Context context) throws ConfigParser.ConfigParseError, IOException, JSONException { boolean preferUDP = PreferenceHelper.getPreferUDP(context); - VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, apiVersion, preferUDP); + boolean allowExperimentalTransports = PreferenceHelper.allowExperimentalTransports(context); + VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, apiVersion, preferUDP, allowExperimentalTransports); HashMap<Connection.TransportType, VpnProfile> profiles = vpnConfigurationGenerator.generateVpnProfiles(); addProfileInfos(context, profiles); return profiles; @@ -194,6 +195,9 @@ public class Gateway { } public boolean supportsTransport(Connection.TransportType transportType) { + if (transportType == Connection.TransportType.PT) { + return supportsPluggableTransports(); + } return vpnProfiles.get(transportType) != null; } @@ -201,6 +205,15 @@ public class Gateway { return new HashSet<>(vpnProfiles.keySet()); } + public boolean supportsPluggableTransports() { + for (Connection.TransportType transportType : vpnProfiles.keySet()) { + if (transportType.isPluggableTransport() && vpnProfiles.get(transportType) != null) { + return true; + } + } + return false; + } + public int getTimezone() { return timezone; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index a11c7e34..db7dd38c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -43,12 +43,15 @@ import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Connection.TransportType; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.base.models.Location; +import se.leap.bitmaskclient.base.models.Pair; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_KCP; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT; import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS; import static se.leap.bitmaskclient.base.models.Constants.HOST; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; @@ -111,18 +114,18 @@ public class GatewaysManager { * select closest Gateway * @return the n closest Gateway */ - public Gateway select(int nClosest) { + public Pair<Gateway, TransportType> select(int nClosest) { String selectedCity = getPreferredCity(context); return select(nClosest, selectedCity); } - public Gateway select(int nClosest, String city) { - TransportType transportType = getUseBridges(context) ? OBFS4 : OPENVPN; + public Pair<Gateway, TransportType> select(int nClosest, String city) { + TransportType[] transportTypes = getUseBridges(context) ? new TransportType[]{OBFS4, OBFS4_KCP} : new TransportType[]{OPENVPN}; if (presortedList.size() > 0) { - return getGatewayFromPresortedList(nClosest, transportType, city); + return getGatewayFromPresortedList(nClosest, transportTypes, city); } - return getGatewayFromTimezoneCalculation(nClosest, transportType, city); + return getGatewayFromTimezoneCalculation(nClosest, transportTypes, city); } public void updateTransport(TransportType transportType) { @@ -158,7 +161,7 @@ public class GatewaysManager { } else { int index = locationNames.get(gateway.getName()); Location location = locations.get(index); - updateLocation(location, gateway, OBFS4); + updateLocation(location, gateway, PT); updateLocation(location, gateway, OPENVPN); locations.set(index, location); } @@ -173,9 +176,9 @@ public class GatewaysManager { private Location initLocation(String name, Gateway gateway, String preferredCity) { HashMap<TransportType, Double> averageLoadMap = new HashMap<>(); HashMap<TransportType, Integer> numberOfGatewaysMap = new HashMap<>(); - if (gateway.getSupportedTransports().contains(OBFS4)) { - averageLoadMap.put(OBFS4, gateway.getFullness()); - numberOfGatewaysMap.put(OBFS4, 1); + if (gateway.supportsPluggableTransports()) { + averageLoadMap.put(PT, gateway.getFullness()); + numberOfGatewaysMap.put(PT, 1); } if (gateway.getSupportedTransports().contains(OPENVPN)) { averageLoadMap.put(OPENVPN, gateway.getFullness()); @@ -189,7 +192,7 @@ public class GatewaysManager { } private void updateLocation(Location location, Gateway gateway, Connection.TransportType transportType) { - if (gateway.getSupportedTransports().contains(transportType)) { + if (gateway.supportsTransport(transportType)) { double averageLoad = location.getAverageLoad(transportType); int numberOfGateways = location.getNumberOfGateways(transportType); averageLoad = (numberOfGateways * averageLoad + gateway.getFullness()) / (numberOfGateways + 1); @@ -218,35 +221,40 @@ public class GatewaysManager { return Load.getLoadByValue(location.getAverageLoad(transportType)); } - private Gateway getGatewayFromTimezoneCalculation(int nClosest, TransportType transportType, @Nullable String city) { + private Pair<Gateway, TransportType> getGatewayFromTimezoneCalculation(int nClosest, TransportType[] transportTypes, @Nullable String city) { List<Gateway> list = new ArrayList<>(gateways.values()); GatewaySelector gatewaySelector = new GatewaySelector(list); Gateway gateway; int found = 0; int i = 0; while ((gateway = gatewaySelector.select(i)) != null) { - if ((city == null && gateway.supportsTransport(transportType)) || - (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { - if (found == nClosest) { - return gateway; + for (TransportType transportType : transportTypes) { + if ((city == null && gateway.supportsTransport(transportType)) || + (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { + if (found == nClosest) { + return new Pair<>(gateway, transportType); + } + found++; } - found++; } i++; } return null; } - private Gateway getGatewayFromPresortedList(int nClosest, TransportType transportType, @Nullable String city) { + private Pair<Gateway, TransportType> getGatewayFromPresortedList(int nClosest, TransportType[] transportTypes, @Nullable String city) { int found = 0; for (Gateway gateway : presortedList) { - if ((city == null && gateway.supportsTransport(transportType)) || - (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { - if (found == nClosest) { - return gateway; + for (TransportType transportType : transportTypes) { + if ((city == null && gateway.supportsTransport(transportType)) || + (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { + if (found == nClosest) { + return new Pair<>(gateway, transportType); + } + found++; } - found++; } + } return null; } @@ -265,7 +273,7 @@ public class GatewaysManager { } private int getPositionFromPresortedList(VpnProfile profile) { - TransportType transportType = profile.mUsePluggableTransports ? OBFS4 : OPENVPN; + TransportType transportType = profile.getTransportType(); int nClosest = 0; for (Gateway gateway : presortedList) { if (gateway.supportsTransport(transportType)) { @@ -277,9 +285,9 @@ public class GatewaysManager { } return -1; } - + private int getPositionFromTimezoneCalculatedList(VpnProfile profile) { - TransportType transportType = profile.mUsePluggableTransports ? OBFS4 : OPENVPN; + TransportType transportType = profile.getTransportType(); GatewaySelector gatewaySelector = new GatewaySelector(new ArrayList<>(gateways.values())); Gateway gateway; int nClosest = 0; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 5ddb74ab..58732713 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -16,6 +16,25 @@ */ package se.leap.bitmaskclient.eip; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_KCP; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; +import static se.leap.bitmaskclient.base.models.Constants.OPTIONS; +import static se.leap.bitmaskclient.base.models.Constants.PORTS; +import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.base.models.Constants.REMOTE; +import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; +import static se.leap.bitmaskclient.base.models.Constants.TYPE; +import static se.leap.bitmaskclient.base.models.Constants.UDP; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn; +import static se.leap.bitmaskclient.pluggableTransports.Shapeshifter.DISPATCHER_IP; +import static se.leap.bitmaskclient.pluggableTransports.Shapeshifter.DISPATCHER_PORT; + import androidx.annotation.VisibleForTesting; import org.json.JSONArray; @@ -31,50 +50,36 @@ import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; +import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.pluggableTransports.Obfs4Options; -import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; -import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; -import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; -import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; -import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; -import static se.leap.bitmaskclient.base.models.Constants.OPTIONS; -import static se.leap.bitmaskclient.base.models.Constants.PORTS; -import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.base.models.Constants.REMOTE; -import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; -import static se.leap.bitmaskclient.base.models.Constants.TYPE; -import static se.leap.bitmaskclient.base.models.Constants.UDP; -import static se.leap.bitmaskclient.pluggableTransports.Shapeshifter.DISPATCHER_IP; -import static se.leap.bitmaskclient.pluggableTransports.Shapeshifter.DISPATCHER_PORT; - public class VpnConfigGenerator { - private JSONObject generalConfiguration; - private JSONObject gateway; - private JSONObject secrets; + private final JSONObject generalConfiguration; + private final JSONObject gateway; + private final JSONObject secrets; private JSONObject obfs4Transport; - private int apiVersion; - private boolean preferUDP; + private JSONObject obfs4TKcpTransport; + private final int apiVersion; + private final boolean preferUDP; + private final boolean experimentalTransports; public final static String TAG = VpnConfigGenerator.class.getSimpleName(); private final String newLine = System.getProperty("line.separator"); // Platform new line - public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, int apiVersion, boolean preferUDP) throws ConfigParser.ConfigParseError { + public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, int apiVersion, boolean preferUDP, boolean experimentalTransports) throws ConfigParser.ConfigParseError { this.generalConfiguration = generalConfiguration; this.gateway = gateway; this.secrets = secrets; this.apiVersion = apiVersion; this.preferUDP = preferUDP; + this.experimentalTransports = experimentalTransports; checkCapabilities(); } public void checkCapabilities() throws ConfigParser.ConfigParseError { - try { if (apiVersion >= 3) { JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); @@ -82,7 +87,11 @@ public class VpnConfigGenerator { JSONObject transport = supportedTransports.getJSONObject(i); if (transport.getString(TYPE).equals(OBFS4.toString())) { obfs4Transport = transport; - break; + if (!experimentalTransports) { + break; + } + } else if (experimentalTransports && transport.getString(TYPE).equals(OBFS4_KCP.toString())) { + obfs4TKcpTransport = transport; } } } @@ -106,6 +115,13 @@ public class VpnConfigGenerator { e.printStackTrace(); } } + if (supportsObfs4Kcp()) { + try { + profiles.put(OBFS4_KCP, createProfile(OBFS4_KCP)); + } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { + e.printStackTrace(); + } + } return profiles; } @@ -113,6 +129,10 @@ public class VpnConfigGenerator { return obfs4Transport != null; } + private boolean supportsObfs4Kcp() { + return obfs4TKcpTransport != null; + } + private String getConfigurationString(Connection.TransportType transportType) { return generalConfiguration() + newLine @@ -129,18 +149,28 @@ public class VpnConfigGenerator { ConfigParser icsOpenvpnConfigParser = new ConfigParser(); icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); if (transportType == OBFS4) { - icsOpenvpnConfigParser.setObfs4Options(getObfs4Options()); + icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(obfs4Transport, false)); + } else if (transportType == OBFS4_KCP) { + icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(obfs4TKcpTransport, true)); } return icsOpenvpnConfigParser.convertProfile(transportType); } - private Obfs4Options getObfs4Options() throws JSONException { - JSONObject transportOptions = obfs4Transport.getJSONObject(OPTIONS); + private Obfs4Options getObfs4Options(JSONObject transportJson, boolean useUdp) throws JSONException { + JSONObject transportOptions = transportJson.getJSONObject(OPTIONS); String iatMode = transportOptions.getString("iatMode"); String cert = transportOptions.getString("cert"); String port = obfs4Transport.getJSONArray(PORTS).getString(0); String ip = gateway.getString(IP_ADDRESS); - return new Obfs4Options(ip, port, cert, iatMode); + boolean udp = useUdp; + + if (BuildConfig.obfsvpn_pinning) { + cert = BuildConfig.obfsvpn_cert; + port = BuildConfig.obfsvpn_port; + ip = BuildConfig.obfsvpn_port; + udp = BuildConfig.obfsvpn_use_kcp; + } + return new Obfs4Options(ip, port, cert, iatMode, udp); } private String generalConfiguration() { @@ -321,10 +351,27 @@ public class VpnConfigGenerator { return; } + JSONArray ports = obfs4Transport.getJSONArray(PORTS); + if (ports.isNull(0)){ + VpnStatus.logError("Misconfigured provider: no ports defined in obfs4 transport JSON."); + return; + } + String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine; stringBuilder.append(route); - String remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine; - stringBuilder.append(remote); + if (useObfsVpn()) { + String remote; + if (BuildConfig.obfsvpn_pinning) { + remote = REMOTE + " " + BuildConfig.obfsvpn_ip + " " + BuildConfig.obfsvpn_port + newLine; + } else { + remote = REMOTE + " " + ipAddress + " " + ports.getString(0) + newLine; + } + + stringBuilder.append(remote); + } else { + String remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine; + stringBuilder.append(remote); + } } private String secretsConfiguration() { diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java index 2f9cb732..b96f88ca 100644 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java @@ -7,12 +7,15 @@ public class Obfs4Options implements Serializable { public String iatMode; public String remoteIP; public String remotePort; + // openvpn is still using tcp, obfs4 is wrapped in kcp, if udp == true + public boolean udp; - public Obfs4Options(String remoteIP, String remotePort, String cert, String iatMode) { + public Obfs4Options(String remoteIP, String remotePort, String cert, String iatMode, boolean udp) { this.cert = cert; this.iatMode = iatMode; this.remoteIP = remoteIP; this.remotePort = remotePort; + this.udp = udp; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java new file mode 100644 index 00000000..f6c8837e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java @@ -0,0 +1,127 @@ +package se.leap.bitmaskclient.pluggableTransports; + +import android.util.Log; + +import java.util.Observable; +import java.util.Observer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import client.Client_; +import de.blinkt.openvpn.core.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.eip.EipStatus; + +public class ObfsVpnClient implements Observer, client.EventLogger { + + public static final AtomicInteger SOCKS_PORT = new AtomicInteger(4430); + public static final String SOCKS_IP = "127.0.0.1"; + private static final String ERR_BIND = "bind: address already in use"; + + private static final String TAG = ObfsVpnClient.class.getSimpleName(); + private volatile boolean noNetwork; + private final AtomicBoolean pendingNetworkErrorHandling = new AtomicBoolean(false); + private final AtomicInteger reconnectRetry = new AtomicInteger(0); + private static final int MAX_RETRY = 5; + + private final client.Client_ obfsVpnClient; + private final Object LOCK = new Object(); + + public ObfsVpnClient(Obfs4Options options) { + obfsVpnClient = new Client_(options.udp, SOCKS_IP+":"+SOCKS_PORT.get(), options.cert); + obfsVpnClient.setEventLogger(this); + } + + /** + * starts the client + * @return the port ObfsVpn is running on + */ + public int start() { + synchronized (LOCK) { + Log.d(TAG, "aquired LOCK"); + new Thread(this::startSync).start(); + waitUntilStarted(); + Log.d(TAG, "returning LOCK after " + (reconnectRetry.get() + 1) * 200 +" ms"); + } + return SOCKS_PORT.get(); + } + + private void waitUntilStarted() { + int count = -1; + try { + while (count < reconnectRetry.get() && reconnectRetry.get() < MAX_RETRY) { + Thread.sleep(200); + count++; + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void startSync() { + try { + obfsVpnClient.start(); + } catch (Exception e) { + Log.e(TAG, "[obfsvpn] exception: " + e.getLocalizedMessage()); + VpnStatus.logError("[obfsvpn] " + e.getLocalizedMessage()); + if (e.getLocalizedMessage() != null && e.getLocalizedMessage().contains(ERR_BIND) && reconnectRetry.get() < MAX_RETRY) { + reconnectRetry.addAndGet(1); + SOCKS_PORT.addAndGet(1); + obfsVpnClient.setSocksAddr(SOCKS_IP+":"+SOCKS_PORT.get()); + Log.d(TAG, "[obfsvpn] reconnecting on different port... " + SOCKS_PORT.get()); + VpnStatus.logDebug("[obfsvpn] reconnecting on different port... " + SOCKS_PORT.get()); + startSync(); + } else if (noNetwork) { + pendingNetworkErrorHandling.set(true); + } + } + } + + public void stop() { + synchronized (LOCK) { + Log.d(TAG, "stopping obfsVpnClient..."); + try { + obfsVpnClient.stop(); + reconnectRetry.set(0); + SOCKS_PORT.set(4430); + Thread.sleep(100); + } catch (Exception e) { + e.printStackTrace(); + VpnStatus.logError("[obfsvpn] " + e.getLocalizedMessage()); + } + pendingNetworkErrorHandling.set(false); + Log.d(TAG, "stopping obfsVpnClient releasing LOCK ..."); + } + } + + public boolean isStarted() { + return obfsVpnClient.isStarted(); + } + + @Override + public void update(Observable observable, Object arg) { + if (observable instanceof EipStatus) { + EipStatus status = (EipStatus) observable; + if (status.getLevel() == ConnectionStatus.LEVEL_NONETWORK) { + noNetwork = true; + } else { + noNetwork = false; + if (pendingNetworkErrorHandling.getAndSet(false)) { + stop(); + start(); + } + } + } + } + + @Override + public void error(String s) { + VpnStatus.logError("[obfsvpn] " + s); + } + + @Override + public void log(String state, String message) { + VpnStatus.logDebug("[obfsvpn] " + state + " " + message); + } + +} diff --git a/app/src/main/res/layout/f_settings.xml b/app/src/main/res/layout/f_settings.xml index 7b8733cd..f89dc956 100644 --- a/app/src/main/res/layout/f_settings.xml +++ b/app/src/main/res/layout/f_settings.xml @@ -114,5 +114,14 @@ app:subtitle="Connect to a specific Gateway for debugging purposes" /> + <se.leap.bitmaskclient.base.views.IconSwitchEntry + android:id="@+id/experimental_transports" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:text="Experimental transports" + app:singleLine="false" + app:subtitle="These transports might circumvent censorship, but are still in a testing phase" + /> + </LinearLayout> </ScrollView>
\ No newline at end of file |