summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java32
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java54
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EIP.java29
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java69
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java93
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java115
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());
}
}