summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java4
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java20
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java46
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java48
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java51
-rw-r--r--app/src/test/resources/ptdemo_misconfigured_ipv4ipv6.json65
-rw-r--r--app/src/test/resources/ptdemo_misconfigured_ipv6.json64
-rw-r--r--app/src/test/resources/ptdemo_misconfigured_udp.json65
-rw-r--r--app/src/test/resources/ptdemo_misconfigured_udptcp.json66
10 files changed, 427 insertions, 11 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
index 5b4ab361..4a8bcf99 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -714,6 +714,10 @@ public class ConfigParser {
Vector<Vector<String>> connectionBlocks = getAllOption("connection", 1, 1);
+ if (connectionBlocks == null && np.mConnections.length == 0) {
+ throw new ConfigParseError("No --remote or <connection> block found.");
+ }
+
if (np.mConnections.length > 0 && connectionBlocks != null) {
throw new ConfigParseError("Using a <connection> block and --remote is not allowed.");
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
index 813daac5..18e97411 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
@@ -27,6 +27,7 @@ import android.graphics.ColorMatrixColorFilter;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Vibrator;
+import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -37,6 +38,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.fragment.app.DialogFragment;
@@ -267,13 +269,8 @@ public class EipFragment extends Fragment implements Observer {
preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, restartOnBoot).apply();
}
- @OnClick(R.id.gateway_location_button)
- void onButtonClick() {
- handleIcon();
- }
-
@OnClick(R.id.main_button)
- void onVpnStateImageClick() {
+ void onButtonClick() {
handleIcon();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java
index 4248072a..64b51960 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Looper;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -44,10 +45,12 @@ import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Calendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import se.leap.bitmaskclient.BuildConfig;
-import se.leap.bitmaskclient.providersetup.ProviderAPI;
import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.providersetup.ProviderAPI;
import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_BITMASK;
@@ -62,6 +65,7 @@ public class ConfigHelper {
final public static String NG_1024 =
"eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3";
final public static BigInteger G = new BigInteger("2");
+ final public static Pattern IPv4_PATTERN = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$");
public static boolean checkErroneousDownload(String downloadedString) {
try {
@@ -203,6 +207,8 @@ public class ConfigHelper {
(string1 != null && string1.equals(string2));
}
+ @SuppressWarnings("unused")
+ // FatWeb Flavor uses that for auto-update
public static String getApkFileName() {
try {
return BuildConfig.update_apk_url.substring(BuildConfig.update_apk_url.lastIndexOf("/"));
@@ -211,6 +217,8 @@ public class ConfigHelper {
}
}
+ @SuppressWarnings("unused")
+ // FatWeb Flavor uses that for auto-update
public static String getVersionFileName() {
try {
return BuildConfig.version_file_url.substring(BuildConfig.version_file_url.lastIndexOf("/"));
@@ -219,6 +227,8 @@ public class ConfigHelper {
}
}
+ @SuppressWarnings("unused")
+ // FatWeb Flavor uses that for auto-update
public static String getSignatureFileName() {
try {
return BuildConfig.signature_url.substring(BuildConfig.signature_url.lastIndexOf("/"));
@@ -227,4 +237,12 @@ public class ConfigHelper {
}
}
+ public static boolean isIPv4(String ipv4) {
+ if (ipv4 == null) {
+ return false;
+ }
+ Matcher matcher = IPv4_PATTERN.matcher(ipv4);
+ return matcher.matches();
+ }
+
}
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 6fffb403..d72f0936 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
@@ -29,8 +29,10 @@ import java.util.Iterator;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ConfigParser;
+import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.connection.Connection;
import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.base.utils.ConfigHelper;
import se.leap.bitmaskclient.pluggableTransports.Obfs4Options;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
@@ -95,7 +97,11 @@ public class VpnConfigGenerator {
HashMap<Connection.TransportType, VpnProfile> profiles = new HashMap<>();
profiles.put(OPENVPN, createProfile(OPENVPN));
if (supportsObfs4()) {
- profiles.put(OBFS4, createProfile(OBFS4));
+ try {
+ profiles.put(OBFS4, createProfile(OBFS4));
+ } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) {
+ e.printStackTrace();
+ }
}
return profiles;
}
@@ -162,16 +168,18 @@ public class VpnConfigGenerator {
StringBuilder stringBuilder = new StringBuilder();
try {
- String ipAddress = gateway.getString(IP_ADDRESS);
+ String ipAddress = null;
JSONObject capabilities = gateway.getJSONObject(CAPABILITIES);
switch (apiVersion) {
default:
case 1:
case 2:
+ ipAddress = gateway.getString(IP_ADDRESS);
gatewayConfigApiv1(stringBuilder, ipAddress, capabilities);
break;
case 3:
case 4:
+ ipAddress = gateway.optString(IP_ADDRESS);
String ipAddress6 = gateway.optString(IP_ADDRESS6);
String[] ipAddresses = ipAddress6.isEmpty() ?
new String[]{ipAddress} :
@@ -189,6 +197,7 @@ public class VpnConfigGenerator {
if (remotes.endsWith(newLine)) {
remotes = remotes.substring(0, remotes.lastIndexOf(newLine));
}
+
return remotes;
}
@@ -247,6 +256,7 @@ public class VpnConfigGenerator {
private void obfs4GatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
JSONObject obfs4Transport = getTransport(transports, OBFS4);
+ JSONArray protocols = obfs4Transport.getJSONArray(PROTOCOLS);
//for now only use ipv4 gateway the syntax route remote_host 255.255.255.255 net_gateway is not yet working
// https://community.openvpn.net/openvpn/ticket/1161
/*for (String ipAddress : ipAddresses) {
@@ -258,10 +268,38 @@ public class VpnConfigGenerator {
return;
}
- String ipAddress = ipAddresses[ipAddresses.length - 1];
+ // check if at least one address is IPv4, IPv6 is currently not supported for obfs4
+ String ipAddress = null;
+ for (String address : ipAddresses) {
+ if (ConfigHelper.isIPv4(address)) {
+ ipAddress = address;
+ break;
+ }
+ VpnStatus.logWarning("Skipping IP address " + address + " while configuring obfs4.");
+ }
+
+ if (ipAddress == null) {
+ VpnStatus.logError("No matching IPv4 address found to configure obfs4.");
+ return;
+ }
+
+ // check if at least one protocol is TCP, UDP is currently not supported for obfs4
+ boolean hasTcp = false;
+ for (int i = 0; i < protocols.length(); i++) {
+ String protocol = protocols.getString(i);
+ if (protocol.contains("tcp")) {
+ hasTcp = true;
+ }
+ }
+
+ if (!hasTcp) {
+ VpnStatus.logError("obfs4 currently only allows TCP! Skipping obfs4 config for ip " + ipAddress);
+ return;
+ }
+
String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;
stringBuilder.append(route);
- String remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " " + obfs4Transport.getJSONArray(PROTOCOLS).getString(0) + newLine;
+ String remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine;
stringBuilder.append(remote);
}
diff --git a/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java
new file mode 100644
index 00000000..75552226
--- /dev/null
+++ b/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java
@@ -0,0 +1,48 @@
+package se.leap.bitmaskclient.base.utils;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(DataProviderRunner.class)
+public class ConfigHelperTest {
+
+ @DataProvider
+ public static Object[][] dataProviderIPs() {
+ // @formatter:off
+ return new Object[][] {
+ { "0.0.0.0", true },
+ { "1.1.1.1", true },
+ { "8.8.8.8", true },
+ { "127.0.0.1", true },
+ { "255.255.255.255", true },
+ { "200.50.20.10", true },
+ { "97.72.15.12", true },
+ {"02.0.0.0", false},
+ {"10.000.1.1", false},
+ {"256.256.256.256", false},
+ {"127..0.1", false},
+ {"127.0..1", false},
+ {"127.0.0.", false},
+ {"127.0.0", false},
+ {"255.255.255.255.255", false},
+ {"", false},
+ {null, false},
+ };
+ }
+
+
+ @Test
+ @UseDataProvider("dataProviderIPs")
+ public void testisIPv4_validIPs_returnsTrue(String ip, boolean isValidExpected) {
+ assertEquals(isValidExpected, ConfigHelper.isIPv4(ip));
+ }
+} \ No newline at end of file
diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
index ad6f5d7b..56575556 100644
--- a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
@@ -1140,5 +1140,56 @@ public class VpnConfigGeneratorTest {
assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v4_ovpn_udp_tcp.trim()));
}
+ @Test
+ public void testGenerateVpnProfile_v3_ipv6only_allowOpenvpnIPv6Only() throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONArray("gateways").getJSONObject(0);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
+ }
+
+ @Test
+ public void testGenerateVpnProfile_v3_obfs4IPv6_skip() throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONArray("gateways").getJSONObject(0);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertFalse(vpnProfiles.containsKey(OBFS4));
+ }
+
+ @Test
+ public void testGenerateVpnProfile_v3_obfs4IPv4AndIPv6_skipIPv6() throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv4ipv6.json"))).getJSONArray("gateways").getJSONObject(0);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv4ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertTrue(vpnProfiles.containsKey(OBFS4));
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
+ assertEquals(1, vpnProfiles.get(OBFS4).mConnections.length);
+ assertEquals("37.218.247.60/32", vpnProfiles.get(OBFS4).mExcludedRoutes.trim());
+ }
+
+ @Test
+ public void testGenerateVpnProfile_v3_obfs4udp_skip() throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udp.json"))).getJSONArray("gateways").getJSONObject(0);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udp.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertFalse(vpnProfiles.containsKey(OBFS4));
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
+ }
+
+ @Test
+ public void testGenerateVpnProfile_v3_obfs4UDPAndTCP_skipUDP() throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udptcp.json"))).getJSONArray("gateways").getJSONObject(0);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udptcp.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertTrue(vpnProfiles.containsKey(OBFS4));
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
+ assertEquals(1, vpnProfiles.get(OBFS4).mConnections.length);
+ assertFalse(vpnProfiles.get(OBFS4).mConnections[0].isUseUdp());
+ }
} \ No newline at end of file
diff --git a/app/src/test/resources/ptdemo_misconfigured_ipv4ipv6.json b/app/src/test/resources/ptdemo_misconfigured_ipv4ipv6.json
new file mode 100644
index 00000000..5c913c14
--- /dev/null
+++ b/app/src/test/resources/ptdemo_misconfigured_ipv4ipv6.json
@@ -0,0 +1,65 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address6":"2001:db8:123::1058",
+ "ip_address":"37.218.247.60",
+ "location":"Amsterdam"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ }
+ },
+ "openvpn_configuration":{
+ "auth":"SHA1",
+ "cipher":"AES-256-CBC",
+ "keepalive":"10 30",
+ "tls-cipher":"DHE-RSA-AES128-SHA",
+ "tun-ipv6":true,
+ "dev" : "tun",
+ "sndbuf" : "0",
+ "rcvbuf" : "0",
+ "nobind" : true,
+ "persist-key" : true,
+ "comp-lzo" : true,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":2,
+ "version":3
+} \ No newline at end of file
diff --git a/app/src/test/resources/ptdemo_misconfigured_ipv6.json b/app/src/test/resources/ptdemo_misconfigured_ipv6.json
new file mode 100644
index 00000000..736dbb8f
--- /dev/null
+++ b/app/src/test/resources/ptdemo_misconfigured_ipv6.json
@@ -0,0 +1,64 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address6":"2001:db8:123::1058",
+ "location":"Amsterdam"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ }
+ },
+ "openvpn_configuration":{
+ "auth":"SHA1",
+ "cipher":"AES-256-CBC",
+ "keepalive":"10 30",
+ "tls-cipher":"DHE-RSA-AES128-SHA",
+ "tun-ipv6":true,
+ "dev" : "tun",
+ "sndbuf" : "0",
+ "rcvbuf" : "0",
+ "nobind" : true,
+ "persist-key" : true,
+ "comp-lzo" : true,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":2,
+ "version":3
+} \ No newline at end of file
diff --git a/app/src/test/resources/ptdemo_misconfigured_udp.json b/app/src/test/resources/ptdemo_misconfigured_udp.json
new file mode 100644
index 00000000..aa9a0d33
--- /dev/null
+++ b/app/src/test/resources/ptdemo_misconfigured_udp.json
@@ -0,0 +1,65 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "udp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address6":"2001:db8:123::1058",
+ "ip_address":"37.218.247.60",
+ "location":"Amsterdam"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ }
+ },
+ "openvpn_configuration":{
+ "auth":"SHA1",
+ "cipher":"AES-256-CBC",
+ "keepalive":"10 30",
+ "tls-cipher":"DHE-RSA-AES128-SHA",
+ "tun-ipv6":true,
+ "dev" : "tun",
+ "sndbuf" : "0",
+ "rcvbuf" : "0",
+ "nobind" : true,
+ "persist-key" : true,
+ "comp-lzo" : true,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":2,
+ "version":3
+} \ No newline at end of file
diff --git a/app/src/test/resources/ptdemo_misconfigured_udptcp.json b/app/src/test/resources/ptdemo_misconfigured_udptcp.json
new file mode 100644
index 00000000..42d55de9
--- /dev/null
+++ b/app/src/test/resources/ptdemo_misconfigured_udptcp.json
@@ -0,0 +1,66 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "udp",
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address6":"2001:db8:123::1058",
+ "ip_address":"37.218.247.60",
+ "location":"Amsterdam"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ }
+ },
+ "openvpn_configuration":{
+ "auth":"SHA1",
+ "cipher":"AES-256-CBC",
+ "keepalive":"10 30",
+ "tls-cipher":"DHE-RSA-AES128-SHA",
+ "tun-ipv6":true,
+ "dev" : "tun",
+ "sndbuf" : "0",
+ "rcvbuf" : "0",
+ "nobind" : true,
+ "persist-key" : true,
+ "comp-lzo" : true,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":2,
+ "version":3
+} \ No newline at end of file