diff options
author | cyBerta <cyberta@riseup.net> | 2024-08-14 18:30:49 +0200 |
---|---|---|
committer | cyBerta <cyberta@riseup.net> | 2024-08-14 18:30:49 +0200 |
commit | e6c5fdf7f711dd1c6cbe02f1e8b6fbdb5f75fbbb (patch) | |
tree | 07e0b3ac34d30db56b9f598d416313cd00634aa1 /app/src/main/java | |
parent | aff83c2090b8e896a9802e70fbdef2ad2df45325 (diff) |
Refactoring that allows enables Bitmask to handle providers that have more than one obfs4 bridge per host correctly. The refactoring also allows us to filter gateways for transport layer protocols (tcp, udp, kcp) in addition to transport types (openvpn, obfs4, obfs4_hop)
Diffstat (limited to 'app/src/main/java')
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()); } } |