diff options
Diffstat (limited to 'app/src/main')
6 files changed, 268 insertions, 124 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index ae8901e0..511893d7 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -5,6 +5,8 @@ package de.blinkt.openvpn; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; import static se.leap.bitmaskclient.base.utils.ConfigHelper.stringEqual; @@ -50,6 +52,7 @@ import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Locale; @@ -73,10 +76,12 @@ import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.X509Utils; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.ConnectionAdapter; +import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.pluggableTransports.models.Obfs4Options; public class VpnProfile implements Serializable, Cloneable { // Note that this class cannot be moved to core where it belongs since @@ -272,11 +277,20 @@ public class VpnProfile implements Serializable, Cloneable { } @Override + public int hashCode() { + int result =(mGatewayIp != null ? mGatewayIp.hashCode() : 0); + result = 31 * result + Arrays.hashCode(mConnections); + result = 31 * result + mTransportType; + return result; + } + + @Override public boolean equals(Object obj) { if (obj instanceof VpnProfile) { VpnProfile vp = (VpnProfile) obj; return stringEqual(vp.mGatewayIp, mGatewayIp) && - vp.mTransportType == mTransportType; + vp.mTransportType == mTransportType && + Arrays.equals(mConnections, vp.mConnections); } return false; } @@ -315,6 +329,22 @@ public class VpnProfile implements Serializable, Cloneable { return Connection.TransportType.fromInt(mTransportType); } + public @Nullable Obfs4Options getObfs4Options() { + Connection.TransportType transportType = getTransportType(); + if (!(transportType == OBFS4 || transportType == OBFS4_HOP)) { + return null; + } + return ((Obfs4Connection) mConnections[0]).getObfs4Options(); + } + + public String getObfuscationTransportLayerProtocol() { + try { + return getObfs4Options().transport.getProtocols()[0]; + } catch (NullPointerException | ArrayIndexOutOfBoundsException ignore) { + return null; + } + } + public String getName() { if (TextUtils.isEmpty(mName)) return "No profile name"; 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 0b28cbca..9943faff 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 @@ -9,10 +9,13 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.*; import android.text.TextUtils; +import androidx.annotation.NonNull; + import com.google.gson.annotations.JsonAdapter; import java.io.Serializable; import java.util.Locale; +import java.util.Objects; @JsonAdapter(ConnectionAdapter.class) public abstract class Connection implements Serializable, Cloneable { @@ -301,5 +304,54 @@ public abstract class Connection implements Serializable, Cloneable { this.mProxyAuthPassword = proxyAuthPassword; } - public abstract TransportType getTransportType(); + public abstract @NonNull TransportType getTransportType(); + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Connection that)) return false; + + if (mUseUdp != that.mUseUdp) return false; + if (mUseCustomConfig != that.mUseCustomConfig) return false; + if (mEnabled != that.mEnabled) return false; + if (mConnectTimeout != that.mConnectTimeout) return false; + if (mUseProxyAuth != that.mUseProxyAuth) return false; + if (!Objects.equals(mServerName, that.mServerName)) + return false; + if (!Objects.equals(mServerPort, that.mServerPort)) + return false; + if (!Objects.equals(mCustomConfiguration, that.mCustomConfiguration)) + return false; + if (mProxyType != that.mProxyType) return false; + if (!Objects.equals(mProxyName, that.mProxyName)) + return false; + if (!Objects.equals(mProxyPort, that.mProxyPort)) + return false; + if (!Objects.equals(mProxyAuthUser, that.mProxyAuthUser)) + return false; + if (getTransportType() != that.getTransportType()) { + return false; + } + return Objects.equals(mProxyAuthPassword, that.mProxyAuthPassword); + } + + @Override + public int hashCode() { + int result = mServerName != null ? mServerName.hashCode() : 0; + result = 31 * result + (mServerPort != null ? mServerPort.hashCode() : 0); + result = 31 * result + (mUseUdp ? 1 : 0); + result = 31 * result + (mCustomConfiguration != null ? mCustomConfiguration.hashCode() : 0); + result = 31 * result + (mUseCustomConfig ? 1 : 0); + result = 31 * result + (mEnabled ? 1 : 0); + result = 31 * result + mConnectTimeout; + result = 31 * result + (mProxyType != null ? mProxyType.hashCode() : 0); + result = 31 * result + (mProxyName != null ? mProxyName.hashCode() : 0); + result = 31 * result + (mProxyPort != null ? mProxyPort.hashCode() : 0); + result = 31 * result + (mUseProxyAuth ? 1 : 0); + result = 31 * result + (mProxyAuthUser != null ? mProxyAuthUser.hashCode() : 0); + result = 31 * result + (mProxyAuthPassword != null ? mProxyAuthPassword.hashCode() : 0); + result = 31 * result + getTransportType().toInt(); + return result; + } } 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 ed61ca13..42935341 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -33,9 +33,7 @@ import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKI 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.utils.ConfigHelper.ensureNotOnMainThread; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE; @@ -71,8 +69,6 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Closeable; import java.lang.ref.WeakReference; -import java.util.Observable; -import java.util.Observer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -87,7 +83,6 @@ import se.leap.bitmaskclient.base.OnBootReceiver; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import se.leap.bitmaskclient.eip.GatewaysManager.GatewayOptions; /** * EIP is the abstract base class for interacting with and managing the Encrypted @@ -251,8 +246,8 @@ public final class EIP extends JobIntentService implements PropertyChangeListene return; } - GatewayOptions gatewayOptions = gatewaysManager.select(nClosestGateway); - launchActiveGateway(gatewayOptions, nClosestGateway, result); + VpnProfile gatewayOptions = gatewaysManager.selectVpnProfile(nClosestGateway); + launchProfile(gatewayOptions, nClosestGateway, result); if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) { tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result); } else { @@ -266,7 +261,7 @@ public final class EIP extends JobIntentService implements PropertyChangeListene */ private void startEIPAlwaysOnVpn() { GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext()); - GatewayOptions gatewayOptions = gatewaysManager.select(0); + VpnProfile vpnProfile = gatewaysManager.selectVpnProfile(0); Bundle result = new Bundle(); if (shouldUpdateVPNCertificate()) { @@ -274,8 +269,7 @@ public final class EIP extends JobIntentService implements PropertyChangeListene p.setShouldUpdateVpnCertificate(true); ProviderObservable.getInstance().updateProvider(p); } - - launchActiveGateway(gatewayOptions, 0, result); + launchProfile(vpnProfile, 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)); } @@ -317,15 +311,15 @@ public final class EIP extends JobIntentService implements PropertyChangeListene } /** - * starts the VPN and connects to the given gateway + * starts the VPN and connects to the given Gateway the VpnProfile belongs to * - * @param gatewayOptions GatewayOptions model containing a Gateway and the associated transport used to connect + * @param profile VpnProfile which contains all information to setup a OpenVPN connection + * and optionally obfsvpn + * @param nClosestGateway gateway index, indicating the distance to the user + * @param result Bundle containing possible error messages shown to the user */ - private void launchActiveGateway(@Nullable GatewayOptions gatewayOptions, int nClosestGateway, Bundle result) { - VpnProfile profile; - - if (gatewayOptions == null || gatewayOptions.gateway == null || - (profile = gatewayOptions.gateway.getProfile(gatewayOptions.transportType)) == null) { + private void launchProfile(@Nullable VpnProfile profile, int nClosestGateway, Bundle result) { + if (profile == null) { String preferredLocation = getPreferredCity(); if (preferredLocation != null) { setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name), preferredLocation); @@ -379,7 +373,6 @@ public final class EIP extends JobIntentService implements PropertyChangeListene } } - /** * Stop VPN * First checks if the OpenVpnConnection is open then 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 d2592cd7..16c92855 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -38,6 +38,7 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferUDP; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useObfuscationPinning; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.gson.Gson; @@ -45,8 +46,9 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; -import java.util.HashMap; import java.util.HashSet; +import java.util.Set; +import java.util.Vector; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; @@ -75,10 +77,7 @@ public class Gateway { private String name; private int timezone; private int apiVersion; - /** FIXME: We expect here that not more than one obfs4 transport is offered by a gateway, however - * it's possible to setup gateways that have obfs4 over kcp and tcp which result in different VpnProfiles each - */ - private HashMap<Connection.TransportType, VpnProfile> vpnProfiles; + private Vector<VpnProfile> vpnProfiles; /** * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json @@ -190,10 +189,10 @@ public class Gateway { /** * Create and attach the VpnProfile to our gateway object */ - private @NonNull HashMap<Connection.TransportType, VpnProfile> createVPNProfiles(VpnConfigGenerator.Configuration profileConfig) + private @NonNull Vector<VpnProfile> createVPNProfiles(VpnConfigGenerator.Configuration profileConfig) throws ConfigParser.ConfigParseError, IOException, JSONException { VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, profileConfig); - HashMap<Connection.TransportType, VpnProfile> profiles = vpnConfigurationGenerator.generateVpnProfiles(); + Vector<VpnProfile> profiles = vpnConfigurationGenerator.generateVpnProfiles(); return profiles; } @@ -201,28 +200,68 @@ public class Gateway { return name; } - public HashMap<Connection.TransportType, VpnProfile> getProfiles() { + public Vector<VpnProfile> getProfiles() { return vpnProfiles; } - public VpnProfile getProfile(Connection.TransportType transportType) { - return vpnProfiles.get(transportType); + /** + * Returns a VpnProfile that supports a given transport type and any of the given transport + * layer protocols (e.g. TCP, KCP). If multiple VpnProfiles fulfill these requirements, a random + * profile will be chosen. This can currently only occur for obfuscation protocols. + * @param transportType transport type, e.g. openvpn or obfs4 + * @param obfuscationTransportLayerProtocols Vector of transport layer protocols PTs can be based on + * @return + */ + public @Nullable VpnProfile getProfile(Connection.TransportType transportType, @Nullable Set<String> obfuscationTransportLayerProtocols) { + Vector<VpnProfile> results = new Vector<>(); + for (VpnProfile vpnProfile : vpnProfiles) { + if (vpnProfile.getTransportType() == transportType) { + if (!vpnProfile.usePluggableTransports() || + obfuscationTransportLayerProtocols == null || + obfuscationTransportLayerProtocols.contains(vpnProfile.getObfuscationTransportLayerProtocol())) { + results.add(vpnProfile); + } + } + } + if (results.size() == 0) { + return null; + } + int randomIndex = (int) (Math.random() * (results.size())); + return results.get(randomIndex); } - public boolean supportsTransport(Connection.TransportType transportType) { + public boolean hasProfile(VpnProfile profile) { + return vpnProfiles.contains(profile); + } + + /** + * Checks if a transport type is supported by the gateway. + * In case the transport type is an obfuscation transport, you can pass a Vector of required transport layer protocols. + * This way you can filter for TCP based obfs4 traffic versus KCP based obfs4 traffic. + * @param transportType transport type, e.g. openvpn or obfs4 + * @param obfuscationTransportLayerProtocols filters for _any_ of these transport layer protocols (e.g. TCP or KCP) of a given obfuscation transportType, can be omitted if transportType is OPENVPN. + * + * @return + */ + public boolean supportsTransport(Connection.TransportType transportType, @Nullable Set<String> obfuscationTransportLayerProtocols) { if (transportType == PT) { return supportsPluggableTransports(); } - return vpnProfiles.get(transportType) != null; + return getProfile(transportType, obfuscationTransportLayerProtocols) != null; } public HashSet<Connection.TransportType> getSupportedTransports() { - return new HashSet<>(vpnProfiles.keySet()); + HashSet<Connection.TransportType> transportTypes = new HashSet<>(); + for (VpnProfile p : vpnProfiles) { + transportTypes.add(p.getTransportType()); + } + return transportTypes; } public boolean supportsPluggableTransports() { - for (Connection.TransportType transportType : vpnProfiles.keySet()) { - if (transportType.isPluggableTransport() && vpnProfiles.get(transportType) != null) { + for (VpnProfile profile : vpnProfiles) { + Connection.TransportType transportType = profile.getTransportType(); + if (transportType.isPluggableTransport()) { return true; } } 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 9b4d431c..cd85f419 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -22,8 +22,10 @@ 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.KCP; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SORTED_GATEWAYS; +import static se.leap.bitmaskclient.base.models.Constants.TCP; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningCert; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningIP; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningKCP; @@ -46,10 +48,13 @@ import org.json.JSONObject; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Set; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; @@ -101,16 +106,6 @@ public class GatewaysManager { } } - public static class GatewayOptions { - public Gateway gateway; - public TransportType transportType; - - public GatewayOptions(Gateway gateway, TransportType transportType) { - this.gateway = gateway; - this.transportType = transportType; - } - } - private static final String TAG = GatewaysManager.class.getSimpleName(); public static final String PINNED_OBFUSCATION_PROXY = "pinned.obfuscation.proxy"; @@ -130,12 +125,12 @@ public class GatewaysManager { } /** - * select closest Gateway - * @return the n closest Gateway + * selects a VpnProfile of the n closest Gateway or a pinned gateway + * @return VpnProfile of the n closest Gateway or null if no remaining VpnProfiles available */ - public GatewayOptions select(int nClosest) { + public @Nullable VpnProfile selectVpnProfile(int nClosestGateway) { if (PreferenceHelper.useObfuscationPinning()) { - if (nClosest > 2) { + if (nClosestGateway > 2) { // no need to try again the pinned proxy, probably configuration error return null; } @@ -143,19 +138,35 @@ public class GatewaysManager { if (gateway == null) { return null; } - return new GatewayOptions(gateway, OBFS4); + return gateway.getProfile(OBFS4, null); } String selectedCity = getPreferredCity(); - return select(nClosest, selectedCity); + return selectVpnProfile(nClosestGateway, selectedCity); } - public GatewayOptions select(int nClosest, String city) { + /** + * Selects a VPN profile, filtered by distance to the user, transportType and + * optionally by city and transport layer protocol + * @param nClosestGateway + * @param city location filter + * @return VpnProfile of the n closest Gateway or null if no remaining VpnProfiles available + */ + public @Nullable VpnProfile selectVpnProfile(int nClosestGateway, String city) { TransportType[] transportTypes = getUseBridges() ? new TransportType[]{OBFS4, OBFS4_HOP} : new TransportType[]{OPENVPN}; + Set<String> obfuscationTransportLayerProtocols = getObfuscationTransportLayerProtocols(); if (presortedList.size() > 0) { - return getGatewayFromPresortedList(nClosest, transportTypes, city); + return getVpnProfileFromPresortedList(nClosestGateway, transportTypes, obfuscationTransportLayerProtocols, city); } - return getGatewayFromTimezoneCalculation(nClosest, transportTypes, city); + return getVpnProfileFromTimezoneCalculation(nClosestGateway, transportTypes, obfuscationTransportLayerProtocols, city); + } + @Nullable + private static Set<String> getObfuscationTransportLayerProtocols() { + Set<String> obfuscationTransportLayerProtocols = null; + if (getUseBridges()) { + obfuscationTransportLayerProtocols = new HashSet<>(Arrays.asList(TCP, KCP)); + } + return obfuscationTransportLayerProtocols; } public void updateTransport(TransportType transportType) { @@ -239,7 +250,7 @@ public class GatewaysManager { } private void updateLocation(Location location, Gateway gateway, Connection.TransportType transportType) { - if (gateway.supportsTransport(transportType)) { + if (gateway.supportsTransport(transportType, null)) { double averageLoad = location.getAverageLoad(transportType); int numberOfGateways = location.getNumberOfGateways(transportType); averageLoad = (numberOfGateways * averageLoad + gateway.getFullness()) / (numberOfGateways + 1); @@ -277,7 +288,7 @@ public class GatewaysManager { return Load.getLoadByValue(location.getAverageLoad(transportType)); } - private GatewayOptions getGatewayFromTimezoneCalculation(int nClosest, TransportType[] transportTypes, @Nullable String city) { + private VpnProfile getVpnProfileFromTimezoneCalculation(int nClosest, TransportType[] transportTypes, @Nullable Set<String> protocols, @Nullable String city) { List<Gateway> list = new ArrayList<>(gateways.values()); if (gatewaySelector == null) { gatewaySelector = new GatewaySelector(list); @@ -287,10 +298,10 @@ public class GatewaysManager { int i = 0; while ((gateway = gatewaySelector.select(i)) != null) { for (TransportType transportType : transportTypes) { - if ((city == null && gateway.supportsTransport(transportType)) || - (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { + if ((city == null && gateway.supportsTransport(transportType, protocols)) || + (gateway.getName().equals(city) && gateway.supportsTransport(transportType, protocols))) { if (found == nClosest) { - return new GatewayOptions(gateway, transportType); + return gateway.getProfile(transportType, protocols); } found++; } @@ -300,19 +311,18 @@ public class GatewaysManager { return null; } - private GatewayOptions getGatewayFromPresortedList(int nClosest, TransportType[] transportTypes, @Nullable String city) { + private VpnProfile getVpnProfileFromPresortedList(int nClosest, TransportType[] transportTypes, @Nullable Set<String> protocols, @Nullable String city) { int found = 0; for (Gateway gateway : presortedList) { for (TransportType transportType : transportTypes) { - if ((city == null && gateway.supportsTransport(transportType)) || - (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { + if ((city == null && gateway.supportsTransport(transportType, protocols)) || + (gateway.getName().equals(city) && gateway.supportsTransport(transportType, protocols))) { if (found == nClosest) { - return new GatewayOptions(gateway, transportType); + return gateway.getProfile(transportType, protocols); } found++; } } - } return null; } @@ -331,35 +341,28 @@ public class GatewaysManager { } private int getPositionFromPresortedList(VpnProfile profile) { - TransportType transportType = profile.getTransportType(); - int nClosest = 0; + int nClosestGateway = 0; for (Gateway gateway : presortedList) { - if (gateway.supportsTransport(transportType)) { - if (profile.equals(gateway.getProfile(transportType))) { - return nClosest; - } - nClosest++; + if (gateway.hasProfile(profile)) { + return nClosestGateway; } + nClosestGateway++; } return -1; } private int getPositionFromTimezoneCalculatedList(VpnProfile profile) { - TransportType transportType = profile.getTransportType(); if (gatewaySelector == null) { gatewaySelector = new GatewaySelector(new ArrayList<>(gateways.values())); } Gateway gateway; - int nClosest = 0; + int nClosestGateway = 0; int i = 0; - while ((gateway = gatewaySelector.select(i)) != null) { - if (gateway.supportsTransport(transportType)) { - if (profile.equals(gateway.getProfile(transportType))) { - return nClosest; - } - nClosest++; + while ((gateway = gatewaySelector.select(nClosestGateway)) != null) { + if (gateway.hasProfile(profile)) { + return nClosestGateway; } - i++; + nClosestGateway++; } return -1; } 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 8cbc4289..5defa7e6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -31,6 +31,7 @@ import static se.leap.bitmaskclient.base.models.Constants.TCP; import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; import static se.leap.bitmaskclient.base.models.Constants.UDP; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import org.json.JSONArray; @@ -39,17 +40,15 @@ import org.json.JSONObject; import java.io.IOException; import java.io.StringReader; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.Vector; 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 de.blinkt.openvpn.core.connection.Connection.TransportType; -import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.Transport; import se.leap.bitmaskclient.base.utils.ConfigHelper; @@ -60,7 +59,7 @@ public class VpnConfigGenerator { private final JSONObject generalConfiguration; private final JSONObject gateway; private final JSONObject secrets; - HashMap<TransportType, Transport> transports = new HashMap<>(); + Vector<Transport> transports = new Vector<>(); private final int apiVersion; private final boolean preferUDP; private final boolean experimentalTransports; @@ -116,7 +115,7 @@ public class VpnConfigGenerator { JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); for (int i = 0; i < supportedTransports.length(); i++) { Transport transport = Transport.fromJson(supportedTransports.getJSONObject(i)); - transports.put(transport.getTransportType(), transport); + transports.add(transport); } } } catch (Exception e) { @@ -124,32 +123,34 @@ public class VpnConfigGenerator { } } - public HashMap<TransportType, VpnProfile> generateVpnProfiles() throws + public Vector<VpnProfile> generateVpnProfiles() throws ConfigParser.ConfigParseError, NumberFormatException { - HashMap<Connection.TransportType, VpnProfile> profiles = new HashMap<>(); - if (supportsOpenvpn()) { - try { - profiles.put(OPENVPN, createProfile(OPENVPN)); - } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { - e.printStackTrace(); - } - } + Vector<VpnProfile> profiles = new Vector<>(); + if (apiVersion >= 3) { - for (TransportType transportType : transports.keySet()) { - Transport transport = transports.get(transportType); - if (transportType.isPluggableTransport()) { + for (Transport transport : transports){ + if (transport.getTransportType().isPluggableTransport()) { Transport.Options transportOptions = transport.getOptions(); if (!experimentalTransports && transportOptions != null && transportOptions.isExperimental()) { continue; } - try { - profiles.put(transportType, createProfile(transportType)); - } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { - e.printStackTrace(); - } + } else if (transport.getTransportType() == OPENVPN && useObfuscationPinning) { + continue; + } + try { + profiles.add(createProfile(transport)); + } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { + e.printStackTrace(); } } + } else if (supportsOpenvpn()) { + // API v1 - TODO: let's remove support for API v1 soon + try { + profiles.add(createApiv1OpenvpnProfile()); + } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { + e.printStackTrace(); + } } if (profiles.isEmpty()) { throw new ConfigParser.ConfigParseError("No supported transports detected."); @@ -159,14 +160,14 @@ public class VpnConfigGenerator { private boolean supportsOpenvpn() { return !useObfuscationPinning && - ((apiVersion >= 3 && transports.containsKey(OPENVPN)) || - (apiVersion < 3 && !gatewayConfiguration(OPENVPN).isEmpty())); + ((apiVersion >= 3 && getTransport(OPENVPN) != null) || + (apiVersion < 3 && !gatewayConfiguration(null).isEmpty())); } - private String getConfigurationString(TransportType transportType) { + private String getConfigurationString(Transport transport) { return generalConfiguration() + newLine - + gatewayConfiguration(transportType) + + gatewayConfiguration(transport) + newLine + androidCustomizations() + newLine @@ -174,12 +175,13 @@ public class VpnConfigGenerator { } @VisibleForTesting - protected VpnProfile createProfile(TransportType transportType) throws IOException, ConfigParser.ConfigParseError, JSONException { - String configuration = getConfigurationString(transportType); + protected VpnProfile createProfile(Transport transport) throws IOException, ConfigParser.ConfigParseError, JSONException { + TransportType transportType = transport.getTransportType(); + String configuration = getConfigurationString(transport); ConfigParser icsOpenvpnConfigParser = new ConfigParser(); icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); if (transportType == OBFS4 || transportType == OBFS4_HOP) { - icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(transportType)); + icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(transport)); } VpnProfile profile = icsOpenvpnConfigParser.convertProfile(transportType); @@ -191,17 +193,29 @@ public class VpnConfigGenerator { return profile; } - private Obfs4Options getObfs4Options(TransportType transportType) throws JSONException { + @VisibleForTesting + protected VpnProfile createApiv1OpenvpnProfile() throws IOException, ConfigParser.ConfigParseError, JSONException { + String configuration = getConfigurationString(null); + ConfigParser icsOpenvpnConfigParser = new ConfigParser(); + icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); + + VpnProfile profile = icsOpenvpnConfigParser.convertProfile(OPENVPN); + profile.mName = profileName; + profile.mGatewayIp = remoteGatewayIP; + if (excludedApps != null) { + profile.mAllowedAppsVpn = new HashSet<>(excludedApps); + } + return profile; + } + + private Obfs4Options getObfs4Options(Transport transport) throws JSONException { String ip = gateway.getString(IP_ADDRESS); - Transport transport; if (useObfuscationPinning) { transport = new Transport(OBFS4.toString(), new String[]{obfuscationPinningKCP ? KCP : TCP}, new String[]{obfuscationPinningPort}, obfuscationPinningCert); ip = obfuscationPinningIP; - } else { - transport = transports.get(transportType); } return new Obfs4Options(ip, transport); } @@ -229,7 +243,7 @@ public class VpnConfigGenerator { return commonOptions; } - private String gatewayConfiguration(TransportType transportType) { + private String gatewayConfiguration(@Nullable Transport transport) { String configs = ""; StringBuilder stringBuilder = new StringBuilder(); @@ -250,11 +264,13 @@ public class VpnConfigGenerator { String[] ipAddresses = ipAddress6.isEmpty() ? new String[]{ipAddress} : new String[]{ipAddress6, ipAddress}; - - gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses); + if (transport == null) { + throw new NullPointerException("Transport is not allowed to be null in APIv3+"); + } + gatewayConfigMinApiv3(transport, stringBuilder, ipAddresses); break; } - } catch (JSONException e) { + } catch (JSONException | NullPointerException e) { // TODO Auto-generated catch block e.printStackTrace(); } @@ -267,12 +283,21 @@ public class VpnConfigGenerator { return configs; } - private void gatewayConfigMinApiv3(TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses) throws JSONException { - if (transportType.isPluggableTransport()) { - ptGatewayConfigMinApiv3(stringBuilder, ipAddresses, transports.get(transportType)); + private void gatewayConfigMinApiv3(Transport transport, StringBuilder stringBuilder, String[] ipAddresses) throws JSONException { + if (transport.getTransportType().isPluggableTransport()) { + ptGatewayConfigMinApiv3(stringBuilder, ipAddresses, transport); } else { - ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports.get(OPENVPN)); + ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transport); + } + } + + private @Nullable Transport getTransport(TransportType transportType) { + for (Transport transport : transports) { + if (transport.getTransportType() == transportType) { + return transport; + } } + return null; } private void gatewayConfigApiv1(StringBuilder stringBuilder, String ipAddress, JSONObject capabilities) throws JSONException { @@ -290,8 +315,8 @@ public class VpnConfigGenerator { } } - private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, Transport transport) { - if (transport.getProtocols() == null || transport.getPorts() == null) { + private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, @Nullable Transport transport) { + if (transport == null || transport.getProtocols() == null || transport.getPorts() == null) { VpnStatus.logError("Misconfigured provider: missing details for transport openvpn on gateway " + ipAddresses[0]); return; } @@ -426,7 +451,7 @@ public class VpnConfigGenerator { // configuration, so we assume yes return true; } - Transport openvpnTransport = transports.get(OPENVPN); + Transport openvpnTransport = getTransport(OPENVPN); if (openvpnTransport == null) { // the bridge seems to be to be decoupled from the gateway, we can't say if the openvpn gateway // will support this PT and hope the admins configured the gateway correctly @@ -455,6 +480,8 @@ public class VpnConfigGenerator { for (String protocol : ptProtocols) { if (isAllowedProtocol(transport.getTransportType(), protocol)) { return true; + } else { + VpnStatus.logError("Provider - client incompatibility: " + protocol + " is not an allowed transport layer protocol for " + transport.getType()); } } |