summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorcyberta <cyberta@riseup.net>2021-02-28 21:29:39 +0000
committercyberta <cyberta@riseup.net>2021-02-28 21:29:39 +0000
commit665b965194cf24d01b02789adf286950da2e91b2 (patch)
treeb31c4d03d8c2511371184d2fe059a94fb1fd2cae /app/src
parent8bb73089566e78a195f21f4746c9db416abf95a4 (diff)
parent401e28431fcd3a093aa34d429f5bc08abca5d422 (diff)
Merge branch 'beta-release' into 'master'
Beta release See merge request leap/bitmask_android!131
Diffstat (limited to 'app/src')
-rw-r--r--app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java2
-rw-r--r--app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl2
-rw-r--r--app/src/main/java/de/blinkt/openvpn/LaunchVPN.java204
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java64
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java17
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java43
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java22
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java10
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java3
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EIP.java255
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java23
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java47
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java2
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java27
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java48
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java38
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java188
-rw-r--r--app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java2
-rw-r--r--app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java5
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java23
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java281
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java6
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java92
-rw-r--r--app/src/test/resources/v4/ipv6_two_openvpn_one_pt_gateways.json123
-rw-r--r--app/src/test/resources/v4/ptdemo_pt_tcp_udp.eip-service.json66
-rw-r--r--app/src/test/resources/v4/ptdemo_pt_udp_tcp.eip-service.json66
-rw-r--r--app/src/test/resources/v4/riseup.geoip.json20
-rw-r--r--app/src/test/resources/v4/riseup.net.json25
-rw-r--r--app/src/test/resources/v4/riseup.service.json96
30 files changed, 1364 insertions, 437 deletions
diff --git a/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
index 833d1e48..dfb5346b 100644
--- a/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
+++ b/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
@@ -204,7 +204,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
result.putBoolean(BROADCAST_RESULT_KEY, true);
}
} catch (NullPointerException | JSONException e) {
- setErrorResult(result, eipServiceJsonString);
+ setErrorResult(result, R.string.error_json_exception_user_message, null);
}
//TODO: check why the following line is not in production
result.putParcelable(PROVIDER_KEY, provider);
diff --git a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl
index 293c2b6d..98cc3e42 100644
--- a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl
+++ b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl
@@ -23,4 +23,6 @@ interface IOpenVPNServiceInternal {
boolean isVpnRunning();
+ void startWithForegroundNotification();
+
}
diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
index b148d04d..82eb3ca9 100644
--- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
+++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java
@@ -5,29 +5,25 @@
package de.blinkt.openvpn;
-import android.annotation.TargetApi;
import android.app.Activity;
-import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
-import java.io.IOException;
+import androidx.annotation.StringRes;
import de.blinkt.openvpn.core.ConnectionStatus;
-import de.blinkt.openvpn.core.Preferences;
-import de.blinkt.openvpn.core.VPNLaunchHelper;
import de.blinkt.openvpn.core.VpnStatus;
-import se.leap.bitmaskclient.base.MainActivity;
import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.eip.EipCommand;
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE;
+import static se.leap.bitmaskclient.eip.EIP.ERRORS;
import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast;
/**
@@ -57,54 +53,52 @@ import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroad
*/
public class LaunchVPN extends Activity {
- public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID";
- public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName";
- public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow";
- public static final String CLEARLOG = "clearlogconnect";
-
-
private static final int START_VPN_PROFILE = 70;
private static final String TAG = LaunchVPN.class.getName();
+ private VpnProfile selectedProfile;
+ private int selectedGateway;
- private VpnProfile mSelectedProfile;
- private boolean mhideLog = false;
-
- private boolean mCmfixed = false;
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- startVpnFromIntent();
- }
-
- protected void startVpnFromIntent() {
- // Resolve the intent
-
final Intent intent = getIntent();
final String action = intent.getAction();
+ if (!Intent.ACTION_MAIN.equals(action)) {
+ finish();
+ }
- // If the intent is a request to create a shortcut, we'll do that and exit
-
+ VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE);
+ selectedGateway = intent.getExtras().getInt(EIP_N_CLOSEST_GATEWAY, 0);
+ if (profileToConnect == null) {
+ showAlertInMainActivity(R.string.shortcut_profile_notfound);
+ finish();
+ } else {
+ selectedProfile = profileToConnect;
+ }
- if (Intent.ACTION_MAIN.equals(action)) {
- // Check if we need to clear the log
- if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true))
- VpnStatus.clearLog();
+ Intent vpnIntent;
+ try {
+ vpnIntent = VpnService.prepare(this.getApplicationContext());
+ } catch (NullPointerException npe) {
+ showAlertInMainActivity(R.string.vpn_error_establish);
+ finish();
+ return;
+ }
- // we got called to be the starting point, most likely a shortcut
- mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false);
- VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE);
+ if (vpnIntent != null) {
+ // we don't have the permission yet to start the VPN
- if (profileToConnect == null) {
- VpnStatus.logError(R.string.shortcut_profile_notfound);
- // show Log window to display error
- showLogWindow();
- finish();
- } else {
- mSelectedProfile = profileToConnect;
- launchVPN();
+ VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission,
+ ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT);
+ // Start the query
+ try {
+ startActivityForResult(vpnIntent, START_VPN_PROFILE);
+ } catch (ActivityNotFoundException ane) {
+ // Shame on you Sony! At least one user reported that
+ // an official Sony Xperia Arc S image triggers this exception
+ showAlertInMainActivity(R.string.no_vpn_support_image);
}
}
}
@@ -113,13 +107,8 @@ public class LaunchVPN extends Activity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if(requestCode==START_VPN_PROFILE) {
- SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
- boolean showLogWindow = prefs.getBoolean("showlogwindow", true);
-
- if(!mhideLog && showLogWindow)
- showLogWindow();
- VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
+ if(requestCode==START_VPN_PROFILE && resultCode == Activity.RESULT_OK) {
+ EipCommand.launchVPNProfile(getApplicationContext(), selectedProfile, selectedGateway);
finish();
} else if (resultCode == Activity.RESULT_CANCELED) {
@@ -127,110 +116,29 @@ public class LaunchVPN extends Activity {
VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled,
ConnectionStatus.LEVEL_NOTCONNECTED);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
- VpnStatus.logError(R.string.nought_alwayson_warning);
-
- finish();
- }
- }
-
- void showLogWindow() {
-
- Intent startLW = new Intent(getBaseContext(), MainActivity.class);
- startLW.putExtra(MainActivity.ACTION_SHOW_LOG_FRAGMENT, true);
- startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- startActivity(startLW);
-
- }
-
- void showConfigErrorDialog(int vpnok) {
- AlertDialog.Builder d = new AlertDialog.Builder(this);
- d.setTitle(R.string.config_error_found);
- d.setMessage(vpnok);
- d.setPositiveButton(android.R.string.ok, new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- finish();
-
- }
- });
- d.setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- finish();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ showAlertInMainActivity(R.string.nought_alwayson_warning);
}
- });
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1)
- setOnDismissListener(d);
- d.show();
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- private void setOnDismissListener(AlertDialog.Builder d) {
- d.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- finish();
- }
- });
- }
- void launchVPN() {
- int vpnok = mSelectedProfile.checkProfile(this);
- if (vpnok != R.string.no_error_found) {
- showConfigErrorDialog(vpnok);
- return;
- }
-
- Intent intent = null;
- try {
- intent = VpnService.prepare(this.getApplicationContext());
- } catch (NullPointerException npe) {
- tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED);
finish();
- return;
- }
-
- // Check if we want to fix /dev/tun
- SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
- boolean usecm9fix = prefs.getBoolean("useCM9Fix", false);
- boolean loadTunModule = prefs.getBoolean("loadTunModule", false);
-
- if (loadTunModule)
- execeuteSUcmd("insmod /system/lib/modules/tun.ko");
-
- if (usecm9fix && !mCmfixed) {
- execeuteSUcmd("chown system /dev/tun");
- }
-
- if (intent != null) {
- VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission,
- ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT);
- // Start the query
- try {
- startActivityForResult(intent, START_VPN_PROFILE);
- } catch (ActivityNotFoundException ane) {
- // Shame on you Sony! At least one user reported that
- // an official Sony Xperia Arc S image triggers this exception
- VpnStatus.logError(R.string.no_vpn_support_image);
- showLogWindow();
- }
- } else {
- onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
}
+ }
+ void showAlertInMainActivity(@StringRes int errorString) {
+ Bundle result = new Bundle();
+ setErrorResult(result, errorString);
+ tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED, result);
}
- private void execeuteSUcmd(String command) {
- try {
- ProcessBuilder pb = new ProcessBuilder("su", "-c", command);
- Process p = pb.start();
- int ret = p.waitFor();
- if (ret == 0)
- mCmfixed = true;
- } catch (InterruptedException | IOException e) {
- VpnStatus.logException("SU command", e);
- }
+ /**
+ * helper function to add error to result bundle
+ *
+ * @param result - result of an action
+ * @param errorMessageId - id of string resource describing the error
+ */
+ void setErrorResult(Bundle result, @StringRes int errorMessageId) {
+ VpnStatus.logError(errorMessageId);
+ result.putString(ERRORS, getResources().getString(errorMessageId));
+ result.putBoolean(BROADCAST_RESULT_KEY, false);
}
} \ No newline at end of file
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
index 2c1a65dc..9ed2054e 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -8,12 +8,10 @@ package de.blinkt.openvpn.core;
import android.Manifest.permission;
import android.annotation.TargetApi;
import android.app.Notification;
-import android.app.UiModeManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.VpnService;
@@ -24,12 +22,13 @@ import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import androidx.annotation.RequiresApi;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
+import androidx.annotation.RequiresApi;
+
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.Inet6Address;
@@ -45,9 +44,10 @@ import de.blinkt.openvpn.core.VpnStatus.StateListener;
import de.blinkt.openvpn.core.connection.Connection;
import de.blinkt.openvpn.core.connection.Obfs4Connection;
import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.eip.EipStatus;
import se.leap.bitmaskclient.eip.VpnNotificationManager;
-import se.leap.bitmaskclient.pluggableTransports.Shapeshifter;
import se.leap.bitmaskclient.firewall.FirewallManager;
+import se.leap.bitmaskclient.pluggableTransports.Shapeshifter;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
@@ -61,7 +61,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN";
- public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg";
public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat";
public static final String VPNSERVICE_TUN = "vpnservice-tun";
public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
@@ -92,11 +91,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private Shapeshifter shapeshifter;
private FirewallManager firewallManager;
- private static final int PRIORITY_MIN = -2;
- private static final int PRIORITY_DEFAULT = 0;
- private static final int PRIORITY_MAX = 2;
-
-
private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() {
@Override
@@ -118,6 +112,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
public boolean isVpnRunning() throws RemoteException {
return OpenVPNService.this.isVpnRunning();
}
+
+ @Override
+ public void startWithForegroundNotification() throws RemoteException {
+ OpenVPNService.this.startWithForegroundNotification();
+ }
};
// From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
@@ -197,13 +196,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
firewallManager.stop();
}
- private boolean runningOnAndroidTV() {
- UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
- if (uiModeManager == null)
- return false;
- return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
- }
-
synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) {
// Registers BroadcastReceiver to track network connection changes.
IntentFilter filter = new IntentFilter();
@@ -218,8 +210,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
registerReceiver(mDeviceStateReceiver, filter);
VpnStatus.addByteCountListener(mDeviceStateReceiver);
- /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
- addLollipopCMListener(); */
}
synchronized void unregisterDeviceStateReceiver() {
@@ -235,9 +225,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
mDeviceStateReceiver = null;
- /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
- removeLollipopCMListener();*/
-
}
@Override
@@ -281,6 +268,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
@Override
+ public void startWithForegroundNotification() {
+ // Always show notification here to avoid problem with startForeground timeout
+ notificationManager.createOpenVpnNotificationChannel();
+ notificationManager.buildForegroundServiceNotification(EipStatus.getInstance().getLevel(), this::onNotificationBuild);
+ }
+
+ @Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false))
@@ -334,6 +328,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
} else {
/* The intent is null when we are set as always-on or the service has been restarted. */
Log.d(TAG, "Starting VPN due to isAlwaysOn system settings or app crash.");
+ startWithForegroundNotification();
+
mProfile = VpnStatus.getLastConnectedVpnProfile(this);
VpnStatus.logInfo(R.string.service_restarted);
@@ -375,9 +371,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
private void startOpenVPN() {
- /**
- * see change above (l. 292 ff)
- */
//TODO: investigate how connections[n] with n>0 get called during vpn setup (on connection refused?)
// Do we need to check if there's any obfs4 connection in mProfile.mConnections and start
// the dispatcher here? Can we start the dispatcher at a later point of execution, e.g. when
@@ -524,8 +517,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
@Override
public void onCreate() {
super.onCreate();
- notificationManager = new VpnNotificationManager(this, this);
- notificationManager.createOpenVpnNotificationChannel();
+ notificationManager = new VpnNotificationManager(this);
firewallManager = new FirewallManager(this, true);
}
@@ -543,9 +535,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// Just in case unregister for state
VpnStatus.removeStateListener(this);
VpnStatus.flushLog();
- notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_BG_ID);
- notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
firewallManager.onDestroy();
+ notificationManager.cancelAll();
}
private String getTunConfigString() {
@@ -1013,14 +1004,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
if (mProcessThread == null && !mNotificationAlwaysVisible)
return;
- String channel = NOTIFICATION_CHANNEL_NEWSTATUS_ID;
// Display byte count only after being connected
-
if (level == LEVEL_CONNECTED) {
mDisplayBytecount = true;
mConnecttime = System.currentTimeMillis();
- if (!runningOnAndroidTV())
- channel = NOTIFICATION_CHANNEL_BG_ID;
firewallManager.start();
} else {
mDisplayBytecount = false;
@@ -1033,7 +1020,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
VpnStatus.getLastCleanLogMessage(this),
level,
0,
- channel);
+ NOTIFICATION_CHANNEL_NEWSTATUS_ID);
}
@@ -1064,7 +1051,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
null,
LEVEL_CONNECTED,
mConnecttime,
- NOTIFICATION_CHANNEL_BG_ID);
+ NOTIFICATION_CHANNEL_NEWSTATUS_ID);
}
}
@@ -1108,7 +1095,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
getString(resid),
LEVEL_WAITING_FOR_USER_INPUT,
0,
- NOTIFICATION_CHANNEL_BG_ID);
+ NOTIFICATION_CHANNEL_NEWSTATUS_ID);
}
@@ -1117,11 +1104,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
startForeground(notificationId, notification);
}
- @Override
- public void onNotificationStop() {
- stopForeground(true);
- }
-
public void trigger_url_open(String info) {
/*
String channel = NOTIFICATION_CHANNEL_USERREQ_ID;
diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
index 7c742746..540ca043 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
@@ -7,7 +7,6 @@ package de.blinkt.openvpn.core;
import android.annotation.TargetApi;
import android.content.Context;
-import android.content.Intent;
import android.os.Build;
import java.io.File;
@@ -18,7 +17,6 @@ import java.util.Arrays;
import java.util.Vector;
import se.leap.bitmaskclient.R;
-import de.blinkt.openvpn.VpnProfile;
public class VPNLaunchHelper {
private static final String MININONPIEVPN = "nopie_openvpn";
@@ -120,7 +118,6 @@ public class VPNLaunchHelper {
return false;
}
-
return true;
} catch (IOException e) {
VpnStatus.logException(e);
@@ -129,20 +126,6 @@ public class VPNLaunchHelper {
}
-
- public static void startOpenVpn(VpnProfile startprofile, Context context) {
- Intent startVPN = startprofile.prepareStartService(context);
- if (startVPN != null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- //noinspection NewApi
- context.startForegroundService(startVPN);
- else
- context.startService(startVPN);
-
- }
- }
-
-
public static String getConfigFilePath(Context context) {
return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE;
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
index 676e6c82..126c4a98 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
@@ -20,13 +20,14 @@ package se.leap.bitmaskclient.base;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.util.Log;
+
import androidx.annotation.StringRes;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
@@ -35,24 +36,27 @@ import java.util.Observable;
import java.util.Observer;
import se.leap.bitmaskclient.R;
-import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment;
-import se.leap.bitmaskclient.eip.EIP;
-import se.leap.bitmaskclient.eip.EipCommand;
-import se.leap.bitmaskclient.eip.EipSetupListener;
-import se.leap.bitmaskclient.eip.EipSetupObserver;
import se.leap.bitmaskclient.base.fragments.EipFragment;
import se.leap.bitmaskclient.base.fragments.ExcludeAppsFragment;
import se.leap.bitmaskclient.base.fragments.LogFragment;
+import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog;
+import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment;
import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.models.ProviderObservable;
-import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
-import se.leap.bitmaskclient.providersetup.activities.LoginActivity;
import se.leap.bitmaskclient.base.utils.PreferenceHelper;
-import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog;
+import se.leap.bitmaskclient.eip.EIP;
+import se.leap.bitmaskclient.eip.EipCommand;
+import se.leap.bitmaskclient.eip.EipSetupListener;
+import se.leap.bitmaskclient.eip.EipSetupObserver;
+import se.leap.bitmaskclient.providersetup.activities.LoginActivity;
+import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
+import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed;
+import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message;
import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST;
@@ -61,16 +65,14 @@ import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE
import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN;
import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER;
import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences;
+import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE;
-import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed;
-import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message;
-import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE;
-import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences;
public class MainActivity extends AppCompatActivity implements EipSetupListener, Observer, ExcludeAppsFragment.ExcludedAppsCallback {
@@ -256,7 +258,14 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener,
break;
case EIP_ACTION_PREPARE_VPN:
if (resultCode == RESULT_CANCELED) {
- showMainActivityErrorDialog(getString(R.string.vpn_error_establish), ERROR_VPN_PREPARE);
+ String error = resultData.getString(ERRORS);
+ showMainActivityErrorDialog(error, ERROR_VPN_PREPARE);
+ }
+ break;
+ case EIP_ACTION_LAUNCH_VPN:
+ if (resultCode == RESULT_CANCELED) {
+ String error = resultData.getString(ERRORS);
+ showMainActivityErrorDialog(error);
}
break;
}
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 4ff80ea6..615221ae 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
@@ -52,6 +52,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
+import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.VpnStatus;
@@ -175,12 +176,18 @@ public class EipFragment extends Fragment implements Observer {
View view = inflater.inflate(R.layout.f_eip, container, false);
unbinder = ButterKnife.bind(this, view);
- Bundle arguments = getArguments();
- if (arguments != null && arguments.containsKey(ASK_TO_CANCEL_VPN) && arguments.getBoolean(ASK_TO_CANCEL_VPN)) {
- arguments.remove(ASK_TO_CANCEL_VPN);
- setArguments(arguments);
- askToStopEIP();
+ try {
+ Bundle arguments = getArguments();
+ if (arguments != null && arguments.containsKey(ASK_TO_CANCEL_VPN) && arguments.getBoolean(ASK_TO_CANCEL_VPN)) {
+ arguments.remove(ASK_TO_CANCEL_VPN);
+ setArguments(arguments);
+ askToStopEIP();
+ }
+ } catch (IllegalStateException e) {
+ // probably setArguments failed because the fragments state is already saved
+ e.printStackTrace();
}
+
restoreFromSavedInstance(savedInstanceState);
return view;
}
@@ -319,10 +326,7 @@ public class EipFragment extends Fragment implements Observer {
} else {
EipCommand.startVPN(context.getApplicationContext(), false);
}
- vpnStateImage.showProgress();
- routedText.setVisibility(GONE);
- vpnRoute.setVisibility(GONE);
- colorBackgroundALittle();
+ EipStatus.getInstance().updateState("UI_CONNECTING", "", 0, ConnectionStatus.LEVEL_START);
}
protected void stopEipIfPossible() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java
index 4b307f23..f036b411 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java
@@ -111,15 +111,15 @@ public class MainActivityErrorDialog extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
Context applicationContext = getContext().getApplicationContext();
- builder.setMessage(reasonToFail)
- .setNegativeButton(R.string.cancel, (dialog, id) -> {
- });
+ builder.setMessage(reasonToFail);
switch (downloadError) {
case ERROR_INVALID_VPN_CERTIFICATE:
builder.setPositiveButton(R.string.update_certificate, (dialog, which) ->
ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider));
+ builder.setNegativeButton(R.string.cancel, (dialog, id) -> {});
break;
case NO_MORE_GATEWAYS:
+ builder.setNegativeButton(R.string.cancel, (dialog, id) -> {});
if (provider.supportsPluggableTransports()) {
if (getUsePluggableTransports(applicationContext)) {
builder.setPositiveButton(warning_option_try_ovpn, ((dialog, which) -> {
@@ -139,9 +139,7 @@ public class MainActivityErrorDialog extends DialogFragment {
}
break;
case ERROR_VPN_PREPARE:
- builder.setPositiveButton(R.string.retry, (dialog, which) -> {
- EipCommand.startVPN(applicationContext, false);
- });
+ builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { });
break;
default:
break;
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
index e60019fc..a0d295bd 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
@@ -80,7 +80,7 @@ public interface Constants {
String EIP_ACTION_START_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_START_BLOCKING_VPN";
String EIP_ACTION_STOP_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_STOP_BLOCKING_VPN";
String EIP_ACTION_PREPARE_VPN = "se.leap.bitmaskclient.EIP_ACTION_PREPARE_VPN";
- String EIP_ACTION_CONFIGURE_TETHERING = "se.leap.bitmaskclient.EIP_ACTION_CONFIGURE_TETHERING";
+ String EIP_ACTION_LAUNCH_VPN = "se.leap.bitmaskclient.EIP_ACTION_LAUNCH_VPN";
String EIP_RECEIVER = "EIP.RECEIVER";
String EIP_REQUEST = "EIP.REQUEST";
@@ -157,6 +157,7 @@ public interface Constants {
// JSON KEYS
/////////////////////////////////////////////
String IP_ADDRESS = "ip_address";
+ String IP_ADDRESS6 = "ip_address6";
String REMOTE = "remote";
String PORTS = "ports";
String PROTOCOLS = "protocols";
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 66b7c6cf..74226250 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
@@ -22,16 +22,19 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
+import android.net.VpnService;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.util.Log;
+
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.annotation.WorkerThread;
import androidx.core.app.JobIntentService;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
@@ -43,26 +46,31 @@ import java.util.Observer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
+import de.blinkt.openvpn.LaunchVPN;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.Preferences;
import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.connection.Connection;
+import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.OnBootReceiver;
import se.leap.bitmaskclient.base.models.ProviderObservable;
-import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.utils.PreferenceHelper;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
+import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid;
+import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN;
@@ -75,13 +83,13 @@ import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
-import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid;
-import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways;
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports;
+import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE;
import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE;
import static se.leap.bitmaskclient.eip.EIP.EIPErrors.NO_MORE_GATEWAYS;
import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast;
-import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread;
-import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports;
/**
* EIP is the abstract base class for interacting with and managing the Encrypted
@@ -106,6 +114,7 @@ public final class EIP extends JobIntentService implements Observer {
// Service connection to OpenVpnService, shared between threads
private volatile OpenVpnServiceConnection openVpnServiceConnection;
private WeakReference<ResultReceiver> mResultRef = new WeakReference<>(null);
+ private volatile VoidVpnServiceConnection voidVpnServiceConnection;
/**
* Unique job ID for this service.
@@ -116,7 +125,8 @@ public final class EIP extends JobIntentService implements Observer {
UNKNOWN,
ERROR_INVALID_VPN_CERTIFICATE,
NO_MORE_GATEWAYS,
- ERROR_VPN_PREPARE
+ ERROR_VPN_PREPARE,
+ ERROR_INVALID_PROFILE
}
/**
@@ -146,6 +156,10 @@ public final class EIP extends JobIntentService implements Observer {
openVpnServiceConnection.close();
openVpnServiceConnection = null;
}
+ if (voidVpnServiceConnection != null) {
+ voidVpnServiceConnection.close();
+ voidVpnServiceConnection = null;
+ }
}
/**
@@ -175,7 +189,7 @@ public final class EIP extends JobIntentService implements Observer {
int nClosestGateway;
switch (action) {
case EIP_ACTION_START:
- boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, true);
+ boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, false);
nClosestGateway = intent.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0);
startEIP(earlyRoutes, nClosestGateway);
break;
@@ -195,8 +209,9 @@ public final class EIP extends JobIntentService implements Observer {
disconnect();
earlyRoutes();
break;
- case EIP_ACTION_CONFIGURE_TETHERING:
- Log.d(TAG, "TODO: implement tethering configuration");
+ case EIP_ACTION_LAUNCH_VPN:
+ VpnProfile profile = (VpnProfile) intent.getSerializableExtra(PROVIDER_PROFILE);
+ launchProfile(profile);
break;
}
}
@@ -211,11 +226,11 @@ public final class EIP extends JobIntentService implements Observer {
@SuppressLint("ApplySharedPref")
private void startEIP(boolean earlyRoutes, int nClosestGateway) {
Log.d(TAG, "start EIP with early routes: " + earlyRoutes + " and nClosest Gateway: " + nClosestGateway);
+ Bundle result = new Bundle();
if (!eipStatus.isBlockingVpnEstablished() && earlyRoutes) {
- earlyRoutes();
+ earlyRoutes(result);
}
- Bundle result = new Bundle();
if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)) {
preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit();
}
@@ -234,12 +249,11 @@ public final class EIP extends JobIntentService implements Observer {
}
Gateway gateway = gatewaysManager.select(nClosestGateway);
-
- if (launchActiveGateway(gateway, nClosestGateway)) {
- tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK);
- } else {
- setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name));
+ launchActiveGateway(gateway, nClosestGateway, result);
+ if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) {
tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result);
+ } else {
+ tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK);
}
}
@@ -250,9 +264,19 @@ public final class EIP extends JobIntentService implements Observer {
private void startEIPAlwaysOnVpn() {
GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext());
Gateway gateway = gatewaysManager.select(0);
+ Bundle result = new Bundle();
- if (!launchActiveGateway(gateway, 0)) {
- Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!");
+ launchActiveGateway(gateway, 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));
+ }
+ }
+
+ private void earlyRoutes() {
+ Bundle result = new Bundle();
+ earlyRoutes(result);
+ if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){
+ tellToReceiverOrBroadcast(this, EIP_ACTION_START_BLOCKING_VPN, RESULT_CANCELED, result);
}
}
@@ -260,10 +284,27 @@ public final class EIP extends JobIntentService implements Observer {
* Early routes are routes that block traffic until a new
* VpnService is started properly.
*/
- private void earlyRoutes() {
- Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class);
- voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(voidVpnLauncher);
+ private void earlyRoutes(Bundle result) {
+ Intent blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application.
+ if (blockingIntent == null) {
+ try {
+ initVoidVpnServiceConnection();
+ Intent voidVpnService = new Intent(getApplicationContext(), VoidVpnService.class);
+ voidVpnService.setAction(EIP_ACTION_START_BLOCKING_VPN);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ getApplicationContext().startForegroundService(voidVpnService);
+ voidVpnServiceConnection.getService().startWithForegroundNotification();
+ } else {
+ getApplicationContext().startService(voidVpnService);
+ }
+ } catch (InterruptedException | IllegalStateException e) {
+ setErrorResult(result, R.string.vpn_error_establish, null);
+ }
+ } else {
+ Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class);
+ voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(voidVpnLauncher);
+ }
}
/**
@@ -271,21 +312,59 @@ public final class EIP extends JobIntentService implements Observer {
*
* @param gateway to connect to
*/
- private boolean launchActiveGateway(Gateway gateway, int nClosestGateway) {
+ private void launchActiveGateway(Gateway gateway, int nClosestGateway, Bundle result) {
VpnProfile profile;
Connection.TransportType transportType = getUsePluggableTransports(this) ? OBFS4 : OPENVPN;
if (gateway == null ||
(profile = gateway.getProfile(transportType)) == null) {
- return false;
+ setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name));
+ return;
}
- Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT);
- intent.putExtra(PROVIDER_PROFILE, profile);
- intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway);
- LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
- return true;
+ Intent intent;
+ try {
+ intent = VpnService.prepare(getApplicationContext());
+ } catch (NullPointerException npe) {
+ setErrorResult(result, ERROR_VPN_PREPARE.toString(), R.string.vpn_error_establish);
+ return;
+ }
+ if (intent == null) {
+ // vpn has been successfully prepared
+
+ //inform EipSetupObserver about vpn connecting attempt
+ Intent setupObserverIntent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT);
+ setupObserverIntent.putExtra(PROVIDER_PROFILE, profile);
+ setupObserverIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(setupObserverIntent);
+
+ // Check if we need to clear the log
+ if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true))
+ VpnStatus.clearLog();
+
+ // check profile configuration
+ int vpnok = profile.checkProfile(this);
+ if (vpnok != R.string.no_error_found) {
+ VpnStatus.logError(R.string.config_error_found);
+ VpnStatus.logError(vpnok);
+ setErrorResult(result, ERROR_INVALID_PROFILE.toString(), 0);
+ return;
+ }
+
+ //launch profile
+ launchProfile(profile, result);
+
+ } else {
+ // vpn permission is missing
+ Intent permissionIntent = new Intent(getApplicationContext(), LaunchVPN.class);
+ permissionIntent.setAction(Intent.ACTION_MAIN);
+ permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ permissionIntent.putExtra(PROVIDER_PROFILE, profile);
+ permissionIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway);
+ startActivity(permissionIntent);
+ }
}
+
/**
* Stop VPN
* First checks if the OpenVpnConnection is open then
@@ -352,14 +431,16 @@ public final class EIP extends JobIntentService implements Observer {
void setErrorResult(Bundle result, String errorId, @StringRes int errorMessageId, Object... args) {
JSONObject errorJson = new JSONObject();
try {
- String errorMessage;
- if (args != null) {
- errorMessage = getResources().getString(errorMessageId, args);
- } else {
- errorMessage = getResources().getString(errorMessageId);
+ if (errorMessageId != 0) {
+ String errorMessage;
+ if (args != null) {
+ errorMessage = getResources().getString(errorMessageId, args);
+ } else {
+ errorMessage = getResources().getString(errorMessageId);
+ }
+ VpnStatus.logWarning("[EIP] error: " + errorMessage);
+ errorJson.put(ERRORS, errorMessage);
}
- VpnStatus.logWarning("[EIP] error: " + errorMessage);
- errorJson.put(ERRORS, errorMessage);
errorJson.put(ERRORID, errorId);
} catch (JSONException e) {
e.printStackTrace();
@@ -409,6 +490,41 @@ public final class EIP extends JobIntentService implements Observer {
return false;
}
+ /**
+ * binds OpenVPNService to this service, starts it as a foreground service with a profile
+ * @param vpnProfile OpenVPN profile used to create a VPN connection
+ * @param result Bundle containing information about possible errors
+ */
+ private void launchProfile(VpnProfile vpnProfile, Bundle result) {
+ Intent startVPN = vpnProfile.prepareStartService(getApplicationContext());
+ if (startVPN != null) {
+ try {
+ initOpenVpnServiceConnection();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ //noinspection NewApi
+ getApplicationContext().startForegroundService(startVPN);
+ openVpnServiceConnection.getService().startWithForegroundNotification();
+ } else {
+ getApplicationContext().startService(startVPN);
+ }
+ } catch (InterruptedException | IllegalStateException | RemoteException e) {
+ setErrorResult(result, R.string.vpn_error_establish, null);
+ }
+ } else {
+ setErrorResult(result, R.string.vpn_error_establish, null);
+ }
+ }
+
+ private void launchProfile(VpnProfile vpnProfile) {
+ Bundle bundle = new Bundle();
+ launchProfile(vpnProfile, bundle);
+ if (bundle.containsKey(BROADCAST_RESULT_KEY) && !bundle.getBoolean(BROADCAST_RESULT_KEY)) {
+ tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_CANCELED, bundle);
+ } else {
+ tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_OK);
+ }
+ }
+
private @StringRes int getStringResourceForNoMoreGateways() {
if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) {
@@ -437,6 +553,69 @@ public final class EIP extends JobIntentService implements Observer {
}
/**
+ * Assigns a new VoidVpnServiceConnection to EIP's member variable voidVpnServiceConnection.
+ * Only one thread at a time can create the service connection, that will be shared between threads
+ *
+ * @throws InterruptedException thrown if thread gets interrupted
+ * @throws IllegalStateException thrown if this method was not called from a background thread
+ */
+ private void initVoidVpnServiceConnection() throws InterruptedException, IllegalStateException {
+ if (voidVpnServiceConnection == null) {
+ Log.d(TAG, "serviceConnection is still null");
+ voidVpnServiceConnection = new VoidVpnServiceConnection(this);
+ }
+ }
+
+ public static class VoidVpnServiceConnection implements Closeable {
+ private final Context context;
+ private ServiceConnection serviceConnection;
+ private VoidVpnService voidVpnService;
+
+ VoidVpnServiceConnection(Context context) throws InterruptedException, IllegalStateException {
+ this.context = context;
+ ensureNotOnMainThread(context);
+ Log.d(TAG, "initSynchronizedServiceConnection!");
+ initSynchronizedServiceConnection(context);
+ }
+
+ @Override
+ public void close() {
+ context.unbindService(serviceConnection);
+ }
+
+ private void initSynchronizedServiceConnection(final Context context) throws InterruptedException {
+ final BlockingQueue<VoidVpnService> blockingQueue = new LinkedBlockingQueue<>(1);
+ this.serviceConnection = new ServiceConnection() {
+ volatile boolean mConnectedAtLeastOnce = false;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (!mConnectedAtLeastOnce) {
+ mConnectedAtLeastOnce = true;
+ try {
+ VoidVpnService.VoidVpnServiceBinder binder = (VoidVpnService.VoidVpnServiceBinder) service;
+ blockingQueue.put(binder.getService());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+ Intent intent = new Intent(context, VoidVpnService.class);
+ context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ voidVpnService = blockingQueue.take();
+ }
+
+ public VoidVpnService getService() {
+ return voidVpnService;
+ }
+ }
+
+ /**
* Creates a service connection to OpenVpnService.
* The constructor blocks until the service is bound to the given Context.
* Pattern stolen from android.security.KeyChain.java
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java
index 0650e8cd..46704419 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java
@@ -9,14 +9,17 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import de.blinkt.openvpn.VpnProfile;
+
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP;
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.PROVIDER_PROFILE;
/**
* Use this class to send commands to EIP
@@ -73,6 +76,15 @@ public class EipCommand {
execute(context, EIP_ACTION_STOP);
}
+ public static void launchVPNProfile(@NonNull Context context, VpnProfile vpnProfile, Integer closestGateway) {
+ Intent baseIntent = new Intent();
+ baseIntent.putExtra(PROVIDER_PROFILE, vpnProfile);
+ baseIntent.putExtra(EIP_N_CLOSEST_GATEWAY, closestGateway);
+ execute(context, EIP_ACTION_LAUNCH_VPN, null, baseIntent);
+ }
+
+ public static void launchVoidVPN(@NonNull Context context) { execute(context, EIP_ACTION_START_BLOCKING_VPN);}
+
@VisibleForTesting
public static void stopVPN(@NonNull Context context, ResultReceiver resultReceiver) {
execute(context, EIP_ACTION_STOP, resultReceiver, null);
@@ -87,13 +99,4 @@ public class EipCommand {
execute(context, EIP_ACTION_CHECK_CERT_VALIDITY, resultReceiver, null);
}
- public static void configureTethering(@NonNull Context context) {
- execute(context, EIP_ACTION_CONFIGURE_TETHERING);
- }
-
- @VisibleForTesting
- public static void configureTethering(@NonNull Context context, ResultReceiver resultReceiver) {
- execute(context, EIP_ACTION_CONFIGURE_TETHERING);
- }
-
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java
index f35e5e30..1ad5f7d2 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java
@@ -54,10 +54,12 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETU
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN;
import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY;
import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE;
@@ -158,14 +160,14 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
ProviderObservable.getInstance().updateProvider(provider);
PreferenceHelper.storeProviderInPreferences(preferences, provider);
if (EipStatus.getInstance().isDisconnected()) {
- EipCommand.startVPN(context.getApplicationContext(), true);
+ EipCommand.startVPN(context.getApplicationContext(), false);
}
break;
case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:
provider = resultData.getParcelable(PROVIDER_KEY);
ProviderObservable.getInstance().updateProvider(provider);
PreferenceHelper.storeProviderInPreferences(preferences, provider);
- EipCommand.startVPN(context.getApplicationContext(), true);
+ EipCommand.startVPN(context.getApplicationContext(), false);
break;
case CORRECTLY_DOWNLOADED_GEOIP_JSON:
provider = resultData.getParcelable(PROVIDER_KEY);
@@ -212,14 +214,18 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
case EIP_ACTION_START_ALWAYS_ON_VPN:
if (resultCode == RESULT_CANCELED) {
//setup failed
- if (error == EIP.EIPErrors.NO_MORE_GATEWAYS) {
- finishGatewaySetup(false);
- EipCommand.startBlockingVPN(context.getApplicationContext());
- } else {
- //FIXME:
- finishGatewaySetup(false);
- EipCommand.stopVPN(context);
- EipStatus.refresh();
+ switch (error) {
+ case NO_MORE_GATEWAYS:
+ finishGatewaySetup(false);
+ EipCommand.startBlockingVPN(context.getApplicationContext());
+ break;
+ case ERROR_INVALID_PROFILE:
+ selectNextGateway();
+ break;
+ default:
+ finishGatewaySetup(false);
+ EipCommand.stopVPN(context);
+ EipStatus.refresh();
}
}
break;
@@ -230,6 +236,13 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
EipStatus.refresh();
}
break;
+ case EIP_ACTION_LAUNCH_VPN:
+ if (resultCode == RESULT_CANCELED) {
+ VpnStatus.logError("Error starting VpnService.");
+ finishGatewaySetup(false);
+ EipStatus.refresh();
+ }
+ break;
default:
break;
}
@@ -252,21 +265,9 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
return;
}
setupVpnProfile = vpnProfile;
- setupNClosestGateway.set(event.getIntExtra(Gateway.KEY_N_CLOSEST_GATEWAY, 0));
+ setupNClosestGateway.set(event.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0));
Log.d(TAG, "bitmaskapp add state listener");
VpnStatus.addStateListener(this);
-
- launchVPN(setupVpnProfile);
- }
-
- private void launchVPN(VpnProfile vpnProfile) {
- Intent intent = new Intent(context.getApplicationContext(), LaunchVPN.class);
- intent.setAction(Intent.ACTION_MAIN);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true);
- intent.putExtra(PROVIDER_PROFILE, vpnProfile);
- intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, setupNClosestGateway.get());
- context.startActivity(intent);
}
@Override
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
index ad84ec5a..bc123683 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
@@ -77,7 +77,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener {
currentStatus.setLocalizedResId(localizedResId);
currentStatus.setLevel(level);
currentStatus.setEipLevel(level);
- if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state)) {
+ if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state) || "UI_CONNECTING".equals(state)) {
refresh();
}
}
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 1df54e6e..6b44856e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
@@ -55,7 +55,6 @@ import static se.leap.bitmaskclient.base.models.Constants.VERSION;
public class Gateway {
public final static String TAG = Gateway.class.getSimpleName();
- public final static String KEY_N_CLOSEST_GATEWAY = "N_CLOSEST_GATEWAY";
private JSONObject generalConfiguration;
private JSONObject secrets;
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java
index e6905448..e2cd86b9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java
@@ -3,10 +3,10 @@ package se.leap.bitmaskclient.eip;
import android.app.Activity;
import android.content.Intent;
import android.net.VpnService;
-import android.os.Build;
import android.os.Bundle;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN;
+import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast;
public class VoidVpnLauncher extends Activity {
@@ -19,24 +19,25 @@ public class VoidVpnLauncher extends Activity {
}
public void setUp() {
- Intent blocking_intent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application.
- if (blocking_intent != null)
- startActivityForResult(blocking_intent, VPN_USER_PERMISSION);
+ Intent blockingIntent = null;
+ try {
+ blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application.
+ } catch (NullPointerException npe) {
+ tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED);
+ finish();
+ }
+ if (blockingIntent != null) {
+ startActivityForResult(blockingIntent, VPN_USER_PERMISSION);
+ }
else {
- onActivityResult(VPN_USER_PERMISSION, RESULT_OK, null);
+ EipCommand.startBlockingVPN(getApplicationContext());
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == VPN_USER_PERMISSION) {
if (resultCode == RESULT_OK) {
- Intent void_vpn_service = new Intent(getApplicationContext(), VoidVpnService.class);
- void_vpn_service.setAction(EIP_ACTION_START_BLOCKING_VPN);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startForegroundService(void_vpn_service);
- } else {
- startService(void_vpn_service);
- }
+ EipCommand.launchVoidVPN(getApplicationContext());
}
}
finish();
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
index 77038492..35d2b376 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
@@ -21,7 +21,9 @@ import android.app.Notification;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.VpnService;
+import android.os.Binder;
import android.os.Build;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.system.OsConstants;
import android.util.Log;
@@ -53,13 +55,27 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat
private EipStatus eipStatus;
private VpnNotificationManager notificationManager;
+ private final IBinder binder = new VoidVpnServiceBinder();
+ public class VoidVpnServiceBinder extends Binder {
+ VoidVpnService getService() {
+ // Return this instance of LocalService so clients can call public methods
+ return VoidVpnService.this;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+
+
@Override
public void onCreate() {
super.onCreate();
eipStatus = EipStatus.getInstance();
eipStatus.addObserver(this);
- notificationManager = new VpnNotificationManager(this, this);
- notificationManager.createVoidVpnNotificationChannel();
+ notificationManager = new VpnNotificationManager(this);
}
@Override
@@ -77,6 +93,7 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat
thread.run();
} else if (action.equals("android.net.VpnService") && Build.VERSION.SDK_INT >= ALWAYS_ON_MIN_API_LEVEL) {
//only always-on feature triggers this
+ startWithForegroundNotification();
thread = new Thread(new Runnable() {
public void run() {
establishBlockingVpn();
@@ -99,14 +116,19 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat
closeFd();
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ notificationManager.cancelAll();
+ }
+
private void stop() {
- notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
- notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
if (thread != null) {
thread.interrupt();
}
closeFd();
VpnStatus.updateStateString("NOPROCESS", "BLOCKING VPN STOPPED", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED);
+ stopForeground(true);
}
public static boolean isRunning() throws NullPointerException {
@@ -185,9 +207,11 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat
notificationManager.buildVoidVpnNotification(
blockingMessage,
blockingMessage,
- eipStatus.getLevel());
+ eipStatus.getLevel(),
+ this
+ );
} else {
- notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+ stopForeground(true);
}
}
@@ -196,9 +220,15 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat
startForeground(notificationId, notification);
}
- @Override
- public void onNotificationStop() {
- stopForeground(true);
+ public void startWithForegroundNotification() {
+ notificationManager.createOpenVpnNotificationChannel();
+ String message = getString(R.string.state_disconnected);
+ notificationManager.buildVoidVpnNotification(
+ message,
+ message,
+ eipStatus.getLevel(),
+ this::onNotificationBuild
+ );
}
}
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 51069d6d..6fffb403 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
@@ -37,6 +37,7 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES;
import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS;
+import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6;
import static se.leap.bitmaskclient.base.models.Constants.OPTIONS;
import static se.leap.bitmaskclient.base.models.Constants.PORTS;
import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS;
@@ -70,7 +71,7 @@ public class VpnConfigGenerator {
public void checkCapabilities() throws ConfigParser.ConfigParseError {
try {
- if (apiVersion == 3) {
+ if (apiVersion >= 3) {
JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT);
for (int i = 0; i < supportedTransports.length(); i++) {
JSONObject transport = supportedTransports.getJSONObject(i);
@@ -170,8 +171,13 @@ public class VpnConfigGenerator {
gatewayConfigApiv1(stringBuilder, ipAddress, capabilities);
break;
case 3:
+ case 4:
+ String ipAddress6 = gateway.optString(IP_ADDRESS6);
+ String[] ipAddresses = ipAddress6.isEmpty() ?
+ new String[]{ipAddress} :
+ new String[]{ipAddress6, ipAddress};
JSONArray transports = capabilities.getJSONArray(TRANSPORT);
- gatewayConfigApiv3(transportType, stringBuilder, ipAddress, transports);
+ gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses, transports);
break;
}
} catch (JSONException e) {
@@ -186,11 +192,11 @@ public class VpnConfigGenerator {
return remotes;
}
- private void gatewayConfigApiv3(Connection.TransportType transportType, StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException {
+ private void gatewayConfigMinApiv3(Connection.TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
if (transportType == OBFS4) {
- obfs4GatewayConfigApiv3(stringBuilder, ipAddress, transports);
+ obfs4GatewayConfigMinApiv3(stringBuilder, ipAddresses, transports);
} else {
- ovpnGatewayConfigApi3(stringBuilder, ipAddress, transports);
+ ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports);
}
}
@@ -209,7 +215,7 @@ public class VpnConfigGenerator {
}
}
- private void ovpnGatewayConfigApi3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException {
+ private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
String port;
String protocol;
JSONObject openvpnTransport = getTransport(transports, OPENVPN);
@@ -219,8 +225,10 @@ public class VpnConfigGenerator {
JSONArray protocols = openvpnTransport.getJSONArray(PROTOCOLS);
for (int k = 0; k < protocols.length(); k++) {
protocol = protocols.optString(k);
- String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
- stringBuilder.append(newRemote);
+ for (String ipAddress : ipAddresses) {
+ String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
+ stringBuilder.append(newRemote);
+ }
}
}
}
@@ -237,8 +245,20 @@ public class VpnConfigGenerator {
return selectedTransport;
}
- private void obfs4GatewayConfigApiv3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException {
+ private void obfs4GatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
JSONObject obfs4Transport = getTransport(transports, OBFS4);
+ //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) {
+ String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;
+ stringBuilder.append(route);
+ }*/
+
+ if (ipAddresses.length == 0) {
+ return;
+ }
+
+ String ipAddress = ipAddresses[ipAddresses.length - 1];
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;
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java
index b3ed5394..6fac0f72 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java
@@ -27,32 +27,34 @@ import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationManagerCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.widget.RemoteViews;
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+
import de.blinkt.openvpn.LaunchVPN;
import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.OpenVPNService;
-import se.leap.bitmaskclient.base.MainActivity;
+import de.blinkt.openvpn.core.VpnStatus;
import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.base.MainActivity;
import se.leap.bitmaskclient.base.StartActivity;
import static android.os.Build.VERSION_CODES.O;
-import static androidx.core.app.NotificationCompat.PRIORITY_HIGH;
-import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
-import static androidx.core.app.NotificationCompat.PRIORITY_MIN;
import static android.text.TextUtils.isEmpty;
+import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT;
+import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
+import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT;
import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN;
-import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT;
/**
* Created by cyberta on 14.01.18.
@@ -60,29 +62,20 @@ import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT;
public class VpnNotificationManager {
+ private static final String TAG = VpnNotificationManager.class.getSimpleName();
Context context;
- private VpnServiceCallback vpnServiceCallback;
- private NotificationManager notificationManager;
- private NotificationManagerCompat compatNotificationManager;
- private String[] notificationChannels = {
- OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
- OpenVPNService.NOTIFICATION_CHANNEL_BG_ID,
- VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID};
- private String lastNotificationChannel = "";
+ private final NotificationManagerCompat compatNotificationManager;
public interface VpnServiceCallback {
void onNotificationBuild(int notificationId, Notification notification);
- void onNotificationStop();
}
- public VpnNotificationManager(@NonNull Context context, @NonNull VpnServiceCallback vpnServiceCallback) {
+ public VpnNotificationManager(@NonNull Context context) {
this.context = context;
- notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
compatNotificationManager = NotificationManagerCompat.from(context);
- this.vpnServiceCallback = vpnServiceCallback;
}
- public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status) {
+ public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status, VpnServiceCallback callback) {
//TODO: implement extra Dashboard.ACTION_ASK_TO_CANCEL_BLOCKING_VPN
NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(R.drawable.ic_menu_close_clear_cancel,
context.getString(R.string.vpn_button_turn_off_blocking), getStopVoidVpnIntent());
@@ -97,28 +90,45 @@ public class VpnNotificationManager {
PRIORITY_MAX,
0,
getMainActivityIntent(),
- actionBuilder.build());
+ actionBuilder.build(),
+ callback
+ );
}
- public void stopNotifications(String notificationChannelNewstatusId) {
- vpnServiceCallback.onNotificationStop();
- compatNotificationManager.cancel(notificationChannelNewstatusId.hashCode());
- }
- public void deleteNotificationChannel(String notificationChannel) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
- notificationManager.getNotificationChannel(notificationChannel) != null) {
- notificationManager.deleteNotificationChannel(notificationChannel);
+ public void buildForegroundServiceNotification(ConnectionStatus connectionStatus,
+ VpnServiceCallback callback) {
+ String message = "";
+ // if the app was killed by the system getLastCleanLogMessage returns an empty string
+ // because the state doesn't get persisted. We can use LEVEL_NOTCONNECTED as an indicator for
+ // that case because the openvpn service won't be connected then
+ if (connectionStatus == ConnectionStatus.LEVEL_NOTCONNECTED) {
+ message = context.getString(R.string.eip_state_not_connected);
+ } else {
+ message = VpnStatus.getLastCleanLogMessage(context);
}
+
+ NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(0,
+ context.getString(R.string.vpn_button_turn_on), getStartOpenvpnIntent());
+
+ buildVpnNotification(
+ "",
+ message,
+ null,
+ "",
+ connectionStatus,
+ OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ PRIORITY_DEFAULT,
+ 0,
+ getMainActivityIntent(),
+ actionBuilder.build(),
+ callback
+ );
}
- /**
- * @param msg
- * @param tickerText
- * @param status
- * @param when
- */
- public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) {
+ public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg,
+ String tickerText, ConnectionStatus status, long when,
+ String notificationChannelNewstatusId, VpnServiceCallback vpnServiceCallback) {
String cancelString;
CharSequence bigmessage = null;
String ghostIcon = new String(Character.toChars(0x1f309));
@@ -158,7 +168,7 @@ public class VpnNotificationManager {
String appName = context.getString(R.string.app_name);
if (isEmpty(profileName)) {
title = appName;
- } else {
+ } else {
title = context.getString(R.string.notifcation_title_bitmask, appName, profileName);
}
@@ -167,15 +177,6 @@ public class VpnNotificationManager {
contentIntent = getUserInputIntent(msg);
else
contentIntent = getMainActivityIntent();
-
- int priority;
- if (OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID.equals(notificationChannelNewstatusId)) {
- priority = PRIORITY_HIGH;
- } else {
- // background channel
- priority = PRIORITY_MIN;
- }
-
buildVpnNotification(
title,
msg,
@@ -183,10 +184,19 @@ public class VpnNotificationManager {
tickerText,
status,
notificationChannelNewstatusId,
- priority,
+ PRIORITY_DEFAULT,
when,
contentIntent,
- actionBuilder.build());
+ actionBuilder.build(),
+ vpnServiceCallback);
+ }
+
+ public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) {
+ buildOpenVpnNotification(profileName, isObfuscated, msg, tickerText, status, when, notificationChannelNewstatusId, null);
+ }
+
+ public void cancelAll() {
+ compatNotificationManager.cancelAll();
}
@@ -197,16 +207,19 @@ public class VpnNotificationManager {
}
// Connection status change messages
- CharSequence name = context.getString(R.string.channel_name_status);
- NotificationChannel mChannel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
- name, NotificationManager.IMPORTANCE_DEFAULT);
-
- mChannel.setDescription(context.getString(R.string.channel_description_status));
- mChannel.enableLights(true);
-
- mChannel.setLightColor(Color.BLUE);
- mChannel.setSound(null, null);
- notificationManager.createNotificationChannel(mChannel);
+ NotificationChannel channel = compatNotificationManager.getNotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+ if (channel == null) {
+ CharSequence name = context.getString(R.string.channel_name_status);
+ channel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name, NotificationManager.IMPORTANCE_DEFAULT);
+
+ channel.setDescription(context.getString(R.string.channel_description_status));
+ channel.enableLights(true);
+
+ channel.setLightColor(Color.BLUE);
+ channel.setSound(null, null);
+ compatNotificationManager.createNotificationChannel(channel);
+ }
}
@TargetApi(O)
@@ -215,29 +228,20 @@ public class VpnNotificationManager {
return;
}
- // Background message
- CharSequence name = context.getString(R.string.channel_name_background);
- NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID,
- name, NotificationManager.IMPORTANCE_MIN);
-
- mChannel.setDescription(context.getString(R.string.channel_description_background));
- mChannel.enableLights(false);
-
- mChannel.setLightColor(Color.DKGRAY);
- notificationManager.createNotificationChannel(mChannel);
-
// Connection status change messages
- name = context.getString(R.string.channel_name_status);
- mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
- name, NotificationManager.IMPORTANCE_DEFAULT);
-
-
- mChannel.setDescription(context.getString(R.string.channel_description_status));
- mChannel.enableLights(true);
-
- mChannel.setLightColor(Color.BLUE);
- mChannel.setSound(null, null);
- notificationManager.createNotificationChannel(mChannel);
+ NotificationChannel channel = compatNotificationManager.getNotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+ if (channel == null) {
+ CharSequence name = context.getString(R.string.channel_name_status);
+ channel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name, NotificationManager.IMPORTANCE_DEFAULT);
+
+ channel.setDescription(context.getString(R.string.channel_description_status));
+ channel.enableLights(true);
+
+ channel.setLightColor(Color.BLUE);
+ channel.setSound(null, null);
+ compatNotificationManager.createNotificationChannel(channel);
+ }
}
/**
@@ -253,7 +257,9 @@ public class VpnNotificationManager {
return remoteViews;
}
- private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText, ConnectionStatus status, String notificationChannelNewstatusId, int priority, long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction) {
+ private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText,
+ ConnectionStatus status, String notificationChannelNewstatusId, int priority,
+ long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction, VpnServiceCallback vpnServiceCallback) {
NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(context, notificationChannelNewstatusId);
int icon = getIconByConnectionStatus(status);
@@ -291,16 +297,10 @@ public class VpnNotificationManager {
Notification notification = nCompatBuilder.build();
int notificationId = notificationChannelNewstatusId.hashCode();
- if (!notificationChannelNewstatusId.equals(lastNotificationChannel)) {
- // Cancel old notification
- for (String channel : notificationChannels) {
- stopNotifications(channel);
- }
- }
-
compatNotificationManager.notify(notificationId, notification);
- vpnServiceCallback.onNotificationBuild(notificationId, notification);
- lastNotificationChannel = notificationChannelNewstatusId;
+ if (vpnServiceCallback != null) {
+ vpnServiceCallback.onNotificationBuild(notificationId, notification);
+ }
}
private PendingIntent getMainActivityIntent() {
@@ -308,6 +308,12 @@ public class VpnNotificationManager {
return PendingIntent.getActivity(context, 0, startActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
+ private PendingIntent getStartOpenvpnIntent() {
+ Intent startIntent = new Intent(context, EIP.class);
+ startIntent.setAction(EIP_ACTION_START);
+ return PendingIntent.getService(context, 0, startIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
private PendingIntent getStopVoidVpnIntent() {
Intent stopVoidVpnIntent = new Intent (context, VoidVpnService.class);
stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN);
diff --git a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
index 96d8ea69..70652365 100644
--- a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
+++ b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
@@ -180,7 +180,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
result.putBoolean(BROADCAST_RESULT_KEY, true);
}
} catch (NullPointerException | JSONException e) {
- setErrorResult(result, eipServiceJsonString);
+ setErrorResult(result, R.string.error_json_exception_user_message, null);
}
return result;
}
diff --git a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java b/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java
index 729c75ed..0fdc94ca 100644
--- a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java
+++ b/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java
@@ -53,6 +53,11 @@ public class TestSetupHelper {
return getProvider(null, null, null, null, null, null, null, null);
}
+ public static Provider getConfiguredProviderAPIv4() {
+ return getProvider(null, null, null, null, null, "v4/riseup.net.json", "v4/riseup.service.json", null);
+ }
+
+
public static Provider getProvider(String domain, String geoipUrl, String providerIp, String providerApiIp, String caCertFile, String providerJson, String eipServiceJson, String geoIpJson) {
if (domain == null)
domain = "https://riseup.net";
diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
index 38449c20..dbcb86b8 100644
--- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
@@ -63,6 +63,7 @@ import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockPr
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_CASE_UPDATED_CERTIFICATE;
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_GEOIP_SERVICE_IS_DOWN;
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.NO_ERROR;
+import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.NO_ERROR_API_V4;
import static se.leap.bitmaskclient.testutils.MockHelper.mockBundle;
import static se.leap.bitmaskclient.testutils.MockHelper.mockClientGenerator;
import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper;
@@ -74,6 +75,7 @@ import static se.leap.bitmaskclient.testutils.MockHelper.mockResources;
import static se.leap.bitmaskclient.testutils.MockHelper.mockResultReceiver;
import static se.leap.bitmaskclient.testutils.MockHelper.mockTextUtils;
import static se.leap.bitmaskclient.testutils.TestSetupHelper.getConfiguredProvider;
+import static se.leap.bitmaskclient.testutils.TestSetupHelper.getConfiguredProviderAPIv4;
import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString;
import static se.leap.bitmaskclient.testutils.TestSetupHelper.getProvider;
@@ -559,4 +561,25 @@ public class ProviderApiManagerTest {
providerApiManager.handleIntent(providerApiCommand);
}
+ @Test
+ public void test_handleIntentSetupProvider_APIv4_happyPath() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
+ Provider provider = getConfiguredProviderAPIv4();
+
+ mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockProviderApiConnector(NO_ERROR_API_V4);
+ providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
+ Bundle expectedResult = mockBundle();
+
+ expectedResult.putBoolean(BROADCAST_RESULT_KEY, true);
+ expectedResult.putParcelable(PROVIDER_KEY, provider);
+
+ Intent providerApiCommand = mockIntent();
+
+ providerApiCommand.putExtra(PROVIDER_KEY, provider);
+ providerApiCommand.setAction(ProviderAPI.SET_UP_PROVIDER);
+ providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_OK, expectedResult));
+
+ providerApiManager.handleIntent(providerApiCommand);
+ }
+
}
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 7d6d1715..ad6f5d7b 100644
--- a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
@@ -12,6 +12,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
+import org.mockito.internal.util.StringUtil;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@@ -25,6 +26,7 @@ import se.leap.bitmaskclient.testutils.TestSetupHelper;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -32,6 +34,7 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
+import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION;
import static se.leap.bitmaskclient.testutils.MockHelper.mockTextUtils;
/**
@@ -763,6 +766,257 @@ public class VpnConfigGeneratorTest {
"keepalive 10 30 \n" +
"tls-cipher DHE-RSA-AES128-SHA \n";
+ String expectedVPNConfig_v4_ovpn_tcp_udp = "# Config for OpenVPN 2.x\n" +
+ "# Enables connection to GUI\n" +
+ "management /data/data/se.leap.bitmask/mgmtsocket unix\n" +
+ "management-client\n" +
+ "management-query-passwords\n" +
+ "management-hold\n" +
+ "\n" +
+ "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" +
+ "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" +
+ "machine-readable-output\n" +
+ "allow-recursive-routing\n" +
+ "ifconfig-nowarn\n" +
+ "client\n" +
+ "verb 4\n" +
+ "connect-retry 2 300\n" +
+ "resolv-retry 60\n" +
+ "dev tun\n" +
+ "remote 2001:db8:123::1056 1195 tcp-client\n" +
+ "remote 37.218.247.60 1195 tcp-client\n" +
+ "remote 2001:db8:123::1056 1195 udp\n" +
+ "remote 37.218.247.60 1195 udp\n" +
+ "<ca>\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt\n" +
+ "YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v\n" +
+ "Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw\n" +
+ "FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV\n" +
+ "BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\n" +
+ "ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai\n" +
+ "dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB\n" +
+ "7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84\n" +
+ "CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+\n" +
+ "znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4\n" +
+ "MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4\n" +
+ "lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0\n" +
+ "bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl\n" +
+ "DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB\n" +
+ "lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy\n" +
+ "YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw\n" +
+ "XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE\n" +
+ "MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w\n" +
+ "DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl\n" +
+ "cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY\n" +
+ "k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj\n" +
+ "RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG\n" +
+ "htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX\n" +
+ "EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J\n" +
+ "aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l\n" +
+ "mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK\n" +
+ "G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co\n" +
+ "Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d\n" +
+ "69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e\n" +
+ "yV8e\n" +
+ "-----END CERTIFICATE-----\n" +
+ "\n" +
+ "</ca>\n" +
+ "<key>\n" +
+ "-----BEGIN RSA PRIVATE KEY-----\n" +
+ "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDUTYWeGgsHS+fjijmziniNqw6h\n" +
+ "MBpyK4S/cM6PxV28C33VuOWPTMcIYesctjZANWFCggfFTQSjV5Qaxq9UK4i27tayLbCdlVS6hpbl\n" +
+ "Vf4DuI3Gj1Pv1rtITBShtvCf3T7yBnjW4wVpOpsUAAOViKUSvUU3kPPMFWhiGQw8yHYr82ts6XMo\n" +
+ "jwMoonW5Ml4e7C7Cr22QesC63q7emNcpUd0pZGT9C33RgDAHZDMrlyjo4HEp1JbUfB0gbmXElJbE\n" +
+ "1TNdZ62HhgmMjzTUN1GGrQ1t91AEoEQwaK65o4YSj+yFv6KXZZz5OWaz94tKiN9v26EXtBFmRlyb\n" +
+ "6+D9ynSd9LghAgMBAAECggEBANPHLRXkhsHVj1EkzqBx7gXr8CEMmiTvknFh9zvltrZhhDoRQjWr\n" +
+ "chPDkcRHY2Cznvy4N0YyqQDD2ULIlZdSAgPxxothFoBruWSD47yMBmLx08ORsDpcqt/YvPAATJI8\n" +
+ "IpFNsXcyaXBp/M57oRemgnxp/8UJPJmFdWX99H4hvffh/jdj7POgYiWUaAl37XTYZKZ4nzKU2wpL\n" +
+ "EDLj9RKPz9gG7CYp2zrLC9LaAsrXVrKwPBw6g+XwbClaqFj97db3mrY4lr6mTo89qmus1AU+fBDH\n" +
+ "3Xlpmc8JwB+30TvhRNKrpLx9cEjuEj7K1gm8Y4dWCjPi+lNbtAyUBcgPJFa/81ECgYEA7pLoBU/Y\n" +
+ "ZYjyHFca8FvDBcBh6haHfqJr9doXWtgjDrbi3o2n5wHqfKhFWOH6vPEQozkOVeX1ze6HOiRmGBpW\n" +
+ "r+r7x8TD25L7I6HJw3M351RWOAfkF0w/RTVdetcTgduQtfN1u6BDhYSVceXMjyQYx7MhfETWI8Gh\n" +
+ "KSYm8OEDYiUCgYEA489fmbrCcUnXzpTsbswJ5NmSoEXbcX8cLxnQuzE0z9GHhQdrMjOpXR76reTW\n" +
+ "6jcuudarNcwRUYSWWhjCDKHhpx4HhasWPaHgr7jIzcRw8yZSJRSxKr8sl1qh6g7s47JcmfXOMWLt\n" +
+ "yuyE933XrT19Th4ODZHY40Uv35mPjMi9d00CgYEAyRNAQtndBRa7GG/B4Ls2T+6pl+aNJIo4e+no\n" +
+ "rURlp800wWabEPRocdBRQmyULBLxduBr2LIMzhgwGSz8b2wji/l9ZA3PFY135bxClVzSzUIjuO3N\n" +
+ "rGUzHl2wAAyuAFDSUshzfkPBJRNt8aVBF5PQ3t93ZYmPAmv8LPZe875yX5ECgYEAsUEcwK/ZNW7g\n" +
+ "dQPZR4iJNkC4Xu6cBZ6Cnn92swBheEYvLSoNlX0vDZ7aLE3/jzQqrjzC8NP8sbH5jtbuvgeDXZX3\n" +
+ "AmGRp5j6C6A61ihAPmEVz3ZfN8SSfJ3vl//PAIg6lyz0J+cy4Q7RkwSeuVQ72Hl4M8TEvmmKC3Af\n" +
+ "ispy6Y0CgYEAgl1o2lo+ACyk+oVQPaaPqK3d7WOBFp4eR2nXFor/vsx9igQOlZUgzRDQsR8jo1o9\n" +
+ "efOSBf87igrZGgssys89pWa2dnXnz5PMmzkKr6bw4D9Ez6u6Puc9UZhGw/8wDYg6fSosdB9utspm\n" +
+ "M698ycef7jBNMDgmhpSvfw5GctoNQ4s=\n" +
+ "-----END RSA PRIVATE KEY-----\n" +
+ "</key>\n" +
+ "<cert>\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIEjDCCAnSgAwIBAgIQG6MBp/cd9DlY+7cdvp3R3jANBgkqhkiG9w0BAQsFADBmMRAwDgYDVQQK\n" +
+ "DAdCaXRtYXNrMRwwGgYDVQQLDBNodHRwczovL2JpdG1hc2submV0MTQwMgYDVQQDDCtCaXRtYXNr\n" +
+ "IFJvb3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTE0MTIwNTAwMDAwMFoXDTE1\n" +
+ "MDMwNTAwMDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEZDBwZDdkMzE4eTNtOHNkeXllaTFqYmZl\n" +
+ "eDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANRNhZ4aCwdL5+OKObOKeI2rDqEwGnIr\n" +
+ "hL9wzo/FXbwLfdW45Y9Mxwhh6xy2NkA1YUKCB8VNBKNXlBrGr1QriLbu1rItsJ2VVLqGluVV/gO4\n" +
+ "jcaPU+/Wu0hMFKG28J/dPvIGeNbjBWk6mxQAA5WIpRK9RTeQ88wVaGIZDDzIdivza2zpcyiPAyii\n" +
+ "dbkyXh7sLsKvbZB6wLrert6Y1ylR3SlkZP0LfdGAMAdkMyuXKOjgcSnUltR8HSBuZcSUlsTVM11n\n" +
+ "rYeGCYyPNNQ3UYatDW33UASgRDBorrmjhhKP7IW/opdlnPk5ZrP3i0qI32/boRe0EWZGXJvr4P3K\n" +
+ "dJ30uCECAwEAAaNvMG0wHQYDVR0OBBYEFK8bMVAM4GBB5sHptoIOAaIvlYueMAsGA1UdDwQEAwIH\n" +
+ "gDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFId+E7bsWFsUWah9\n" +
+ "vZuPvZ7O+aJsMA0GCSqGSIb3DQEBCwUAA4ICAQAQOX81csVhvP422NKkZH7+g3npBpl+sEHedaGR\n" +
+ "xYPOu4HrA4TVF9h44sljRoRJyenGNdBZCXcLKHg889eePTf8Z5K3lTojp6hvwyA6tgxOMHT1kESW\n" +
+ "PfqnRw8mHfHJuE3g+4YNUMwggzwc/VZATdV/7M33sarVN9AUOHou9n9BizgCC+UnYlS+F2POumE3\n" +
+ "FbOhKo5uubI02MwBYlN2JVO2TBt1Q20w8wc6cU07Xi5Epp+1mkgFiOShkNtPcJmEyBWJhxDtSDOW\n" +
+ "2doqWYNqH2kq7B5R/kyyfcpFJqAnBTV7xs+C5rTS1mW7LpxfdCUMbYuLCpyxpO3A/DhAm8n47tUH\n" +
+ "lBtmo8Avdb8VdFpYiGBpB0o9kTFcsWFb2GkWFBduGfSEB8jUI7QtqhgZqocAKK/cweSRV8FwyUcn\n" +
+ "R0prRm3QEi9fbXqEddzjSY9y/lqWYzT7u+IOAQpKroeZ4wzgYperDNOUFuYk1rP7yuvjP2pV5rcN\n" +
+ "yPoBP60TPVWMRM4WJm6nTogAz2qBrFsf/XwT/ajzbsjT6HNB7QbRE+wkFkqspoXG5Agp7KQ8lW3L\n" +
+ "SKCDGOQJz7VIE85pD0tg7QEXBEw8oaRZtMjQ0Gvs25mxXAKka4wGasaWfYH6d0E+iKYcWn86V1rH\n" +
+ "K2ZoknT+Nno5jgjFuUR3fZseNizEfx7BteooKQ==\n" +
+ "-----END CERTIFICATE-----\n" +
+ "</cert>\n" +
+ "crl-verify file missing in config profile\n" +
+ "comp-lzo\n" +
+ "nobind\n"+
+ "remote-cert-tls server\n" +
+ "data-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC\n" +
+ "cipher AES-256-CBC\n" +
+ "auth SHA1\n" +
+ "persist-tun\n" +
+ "# persist-tun also enables pre resolving to avoid DNS resolve problem\n" +
+ "preresolve\n" +
+ "# Custom configuration options\n" +
+ "# You are on your on own here :)\n" +
+ "# These options found in the config file do not map to config settings:\n" +
+ "keepalive 10 30 \n" +
+ "tls-cipher DHE-RSA-AES128-SHA \n" +
+ "sndbuf 0 \n" +
+ "rcvbuf 0 \n";
+
+ String expectedVPNConfig_v4_ovpn_udp_tcp = "# Config for OpenVPN 2.x\n" +
+ "# Enables connection to GUI\n" +
+ "management /data/data/se.leap.bitmask/mgmtsocket unix\n" +
+ "management-client\n" +
+ "management-query-passwords\n" +
+ "management-hold\n" +
+ "\n" +
+ "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" +
+ "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" +
+ "machine-readable-output\n" +
+ "allow-recursive-routing\n" +
+ "ifconfig-nowarn\n" +
+ "client\n" +
+ "verb 4\n" +
+ "connect-retry 2 300\n" +
+ "resolv-retry 60\n" +
+ "dev tun\n" +
+ "remote 2001:db8:123::1056 1195 udp\n" +
+ "remote 37.218.247.60 1195 udp\n" +
+ "remote 2001:db8:123::1056 1195 tcp-client\n" +
+ "remote 37.218.247.60 1195 tcp-client\n" +
+ "<ca>\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt\n" +
+ "YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v\n" +
+ "Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw\n" +
+ "FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV\n" +
+ "BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\n" +
+ "ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai\n" +
+ "dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB\n" +
+ "7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84\n" +
+ "CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+\n" +
+ "znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4\n" +
+ "MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4\n" +
+ "lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0\n" +
+ "bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl\n" +
+ "DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB\n" +
+ "lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy\n" +
+ "YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw\n" +
+ "XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE\n" +
+ "MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w\n" +
+ "DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl\n" +
+ "cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY\n" +
+ "k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj\n" +
+ "RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG\n" +
+ "htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX\n" +
+ "EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J\n" +
+ "aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l\n" +
+ "mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK\n" +
+ "G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co\n" +
+ "Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d\n" +
+ "69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e\n" +
+ "yV8e\n" +
+ "-----END CERTIFICATE-----\n" +
+ "\n" +
+ "</ca>\n" +
+ "<key>\n" +
+ "-----BEGIN RSA PRIVATE KEY-----\n" +
+ "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDUTYWeGgsHS+fjijmziniNqw6h\n" +
+ "MBpyK4S/cM6PxV28C33VuOWPTMcIYesctjZANWFCggfFTQSjV5Qaxq9UK4i27tayLbCdlVS6hpbl\n" +
+ "Vf4DuI3Gj1Pv1rtITBShtvCf3T7yBnjW4wVpOpsUAAOViKUSvUU3kPPMFWhiGQw8yHYr82ts6XMo\n" +
+ "jwMoonW5Ml4e7C7Cr22QesC63q7emNcpUd0pZGT9C33RgDAHZDMrlyjo4HEp1JbUfB0gbmXElJbE\n" +
+ "1TNdZ62HhgmMjzTUN1GGrQ1t91AEoEQwaK65o4YSj+yFv6KXZZz5OWaz94tKiN9v26EXtBFmRlyb\n" +
+ "6+D9ynSd9LghAgMBAAECggEBANPHLRXkhsHVj1EkzqBx7gXr8CEMmiTvknFh9zvltrZhhDoRQjWr\n" +
+ "chPDkcRHY2Cznvy4N0YyqQDD2ULIlZdSAgPxxothFoBruWSD47yMBmLx08ORsDpcqt/YvPAATJI8\n" +
+ "IpFNsXcyaXBp/M57oRemgnxp/8UJPJmFdWX99H4hvffh/jdj7POgYiWUaAl37XTYZKZ4nzKU2wpL\n" +
+ "EDLj9RKPz9gG7CYp2zrLC9LaAsrXVrKwPBw6g+XwbClaqFj97db3mrY4lr6mTo89qmus1AU+fBDH\n" +
+ "3Xlpmc8JwB+30TvhRNKrpLx9cEjuEj7K1gm8Y4dWCjPi+lNbtAyUBcgPJFa/81ECgYEA7pLoBU/Y\n" +
+ "ZYjyHFca8FvDBcBh6haHfqJr9doXWtgjDrbi3o2n5wHqfKhFWOH6vPEQozkOVeX1ze6HOiRmGBpW\n" +
+ "r+r7x8TD25L7I6HJw3M351RWOAfkF0w/RTVdetcTgduQtfN1u6BDhYSVceXMjyQYx7MhfETWI8Gh\n" +
+ "KSYm8OEDYiUCgYEA489fmbrCcUnXzpTsbswJ5NmSoEXbcX8cLxnQuzE0z9GHhQdrMjOpXR76reTW\n" +
+ "6jcuudarNcwRUYSWWhjCDKHhpx4HhasWPaHgr7jIzcRw8yZSJRSxKr8sl1qh6g7s47JcmfXOMWLt\n" +
+ "yuyE933XrT19Th4ODZHY40Uv35mPjMi9d00CgYEAyRNAQtndBRa7GG/B4Ls2T+6pl+aNJIo4e+no\n" +
+ "rURlp800wWabEPRocdBRQmyULBLxduBr2LIMzhgwGSz8b2wji/l9ZA3PFY135bxClVzSzUIjuO3N\n" +
+ "rGUzHl2wAAyuAFDSUshzfkPBJRNt8aVBF5PQ3t93ZYmPAmv8LPZe875yX5ECgYEAsUEcwK/ZNW7g\n" +
+ "dQPZR4iJNkC4Xu6cBZ6Cnn92swBheEYvLSoNlX0vDZ7aLE3/jzQqrjzC8NP8sbH5jtbuvgeDXZX3\n" +
+ "AmGRp5j6C6A61ihAPmEVz3ZfN8SSfJ3vl//PAIg6lyz0J+cy4Q7RkwSeuVQ72Hl4M8TEvmmKC3Af\n" +
+ "ispy6Y0CgYEAgl1o2lo+ACyk+oVQPaaPqK3d7WOBFp4eR2nXFor/vsx9igQOlZUgzRDQsR8jo1o9\n" +
+ "efOSBf87igrZGgssys89pWa2dnXnz5PMmzkKr6bw4D9Ez6u6Puc9UZhGw/8wDYg6fSosdB9utspm\n" +
+ "M698ycef7jBNMDgmhpSvfw5GctoNQ4s=\n" +
+ "-----END RSA PRIVATE KEY-----\n" +
+ "</key>\n" +
+ "<cert>\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIEjDCCAnSgAwIBAgIQG6MBp/cd9DlY+7cdvp3R3jANBgkqhkiG9w0BAQsFADBmMRAwDgYDVQQK\n" +
+ "DAdCaXRtYXNrMRwwGgYDVQQLDBNodHRwczovL2JpdG1hc2submV0MTQwMgYDVQQDDCtCaXRtYXNr\n" +
+ "IFJvb3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTE0MTIwNTAwMDAwMFoXDTE1\n" +
+ "MDMwNTAwMDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEZDBwZDdkMzE4eTNtOHNkeXllaTFqYmZl\n" +
+ "eDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANRNhZ4aCwdL5+OKObOKeI2rDqEwGnIr\n" +
+ "hL9wzo/FXbwLfdW45Y9Mxwhh6xy2NkA1YUKCB8VNBKNXlBrGr1QriLbu1rItsJ2VVLqGluVV/gO4\n" +
+ "jcaPU+/Wu0hMFKG28J/dPvIGeNbjBWk6mxQAA5WIpRK9RTeQ88wVaGIZDDzIdivza2zpcyiPAyii\n" +
+ "dbkyXh7sLsKvbZB6wLrert6Y1ylR3SlkZP0LfdGAMAdkMyuXKOjgcSnUltR8HSBuZcSUlsTVM11n\n" +
+ "rYeGCYyPNNQ3UYatDW33UASgRDBorrmjhhKP7IW/opdlnPk5ZrP3i0qI32/boRe0EWZGXJvr4P3K\n" +
+ "dJ30uCECAwEAAaNvMG0wHQYDVR0OBBYEFK8bMVAM4GBB5sHptoIOAaIvlYueMAsGA1UdDwQEAwIH\n" +
+ "gDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFId+E7bsWFsUWah9\n" +
+ "vZuPvZ7O+aJsMA0GCSqGSIb3DQEBCwUAA4ICAQAQOX81csVhvP422NKkZH7+g3npBpl+sEHedaGR\n" +
+ "xYPOu4HrA4TVF9h44sljRoRJyenGNdBZCXcLKHg889eePTf8Z5K3lTojp6hvwyA6tgxOMHT1kESW\n" +
+ "PfqnRw8mHfHJuE3g+4YNUMwggzwc/VZATdV/7M33sarVN9AUOHou9n9BizgCC+UnYlS+F2POumE3\n" +
+ "FbOhKo5uubI02MwBYlN2JVO2TBt1Q20w8wc6cU07Xi5Epp+1mkgFiOShkNtPcJmEyBWJhxDtSDOW\n" +
+ "2doqWYNqH2kq7B5R/kyyfcpFJqAnBTV7xs+C5rTS1mW7LpxfdCUMbYuLCpyxpO3A/DhAm8n47tUH\n" +
+ "lBtmo8Avdb8VdFpYiGBpB0o9kTFcsWFb2GkWFBduGfSEB8jUI7QtqhgZqocAKK/cweSRV8FwyUcn\n" +
+ "R0prRm3QEi9fbXqEddzjSY9y/lqWYzT7u+IOAQpKroeZ4wzgYperDNOUFuYk1rP7yuvjP2pV5rcN\n" +
+ "yPoBP60TPVWMRM4WJm6nTogAz2qBrFsf/XwT/ajzbsjT6HNB7QbRE+wkFkqspoXG5Agp7KQ8lW3L\n" +
+ "SKCDGOQJz7VIE85pD0tg7QEXBEw8oaRZtMjQ0Gvs25mxXAKka4wGasaWfYH6d0E+iKYcWn86V1rH\n" +
+ "K2ZoknT+Nno5jgjFuUR3fZseNizEfx7BteooKQ==\n" +
+ "-----END CERTIFICATE-----\n" +
+ "</cert>\n" +
+ "crl-verify file missing in config profile\n" +
+ "comp-lzo\n" +
+ "nobind\n"+
+ "remote-cert-tls server\n" +
+ "data-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC\n" +
+ "cipher AES-256-CBC\n" +
+ "auth SHA1\n" +
+ "persist-tun\n" +
+ "# persist-tun also enables pre resolving to avoid DNS resolve problem\n" +
+ "preresolve\n" +
+ "# Custom configuration options\n" +
+ "# You are on your on own here :)\n" +
+ "# These options found in the config file do not map to config settings:\n" +
+ "keepalive 10 30 \n" +
+ "tls-cipher DHE-RSA-AES128-SHA \n" +
+ "sndbuf 0 \n" +
+ "rcvbuf 0 \n";
+
+
@Before
public void setUp() throws Exception {
@@ -860,4 +1114,31 @@ public class VpnConfigGeneratorTest {
System.out.println(vpnProfiles.get(OPENVPN).getConfigFile(context, false));
assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v3_ovpn_udp_tcp_defaultDataCiphers.trim()));
}
+
+ @Test
+ public void testGenerateVpnProfile_v4_ovpn_tcp_udp() throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 4);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertTrue(vpnProfiles.containsKey(OBFS4));
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
+ System.out.println(vpnProfiles.get(OPENVPN).getConfigFile(context, false));
+ assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v4_ovpn_tcp_udp.trim()));
+ }
+
+ @Test
+ public void testGenerateVpnProfile_v4_ovpn_udp_tcp() throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_udp_tcp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_udp_tcp.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 4);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertTrue(vpnProfiles.containsKey(OBFS4));
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
+ System.out.println(vpnProfiles.get(OPENVPN).getConfigFile(context, false));
+ System.out.println(expectedVPNConfig_v4_ovpn_udp_tcp.trim());
+ assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v4_ovpn_udp_tcp.trim()));
+ }
+
+
} \ No newline at end of file
diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java
index 3e8dfd5f..d76e4029 100644
--- a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java
@@ -45,7 +45,8 @@ public class BackendMockProvider {
ERROR_NO_ACCESS,
ERROR_INVALID_SESSION_TOKEN,
ERROR_NO_CONNECTION,
- ERROR_WRONG_SRP_CREDENTIALS
+ ERROR_WRONG_SRP_CREDENTIALS,
+ NO_ERROR_API_V4
}
@@ -55,6 +56,9 @@ public class BackendMockProvider {
case NO_ERROR:
new NoErrorBackendResponse();
break;
+ case NO_ERROR_API_V4:
+ new NoErrorBackendResponseAPIv4();
+ break;
case ERROR_CASE_UPDATED_CERTIFICATE:
new UpdatedCertificateBackendResponse();
break;
diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java
new file mode 100644
index 00000000..3b77834f
--- /dev/null
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.testutils.BackendMockResponses;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+
+import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString;
+
+/**
+ * Created by cyberta on 10.01.18.
+ */
+
+public class NoErrorBackendResponseAPIv4 extends BaseBackendResponse {
+ public NoErrorBackendResponseAPIv4() throws IOException {
+ super();
+ }
+
+ @Override
+ public Answer<String> getAnswerForRequestStringFromServer() {
+ return new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) throws Throwable {
+ String url = (String) invocation.getArguments()[0];
+ String requestMethod = (String) invocation.getArguments()[1];
+ String jsonPayload = (String) invocation.getArguments()[2];
+
+ if (url.contains("/provider.json")) {
+ //download provider json
+ return getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/riseup.net.json"));
+ } else if (url.contains("/ca.crt")) {
+ //download provider ca cert
+ return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"));
+ } else if (url.contains("config/eip-service.json")) {
+ // download provider service json containing gateways, locations and openvpn settings
+ return getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/riseup.service.json"));
+ } else if (url.contains(":9001/json")) {
+ // download geoip json, containing a sorted list of gateways
+ return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.geoip.json"));
+ } else if (url.contains("/users.json")) {
+ //create new user
+ //TODO: implement me
+ } else if (url.contains("/sessions.json")) {
+ //srp auth: sendAToSRPServer
+ //TODO: implement me
+ } else if (url.contains("/sessions/parmegvtest10.json")){
+ //srp auth: sendM1ToSRPServer
+ //TODO: implement me
+ }
+
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public Answer<Boolean> getAnswerForCanConnect() {
+ return new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return true;
+ }
+ };
+ }
+
+ @Override
+ public Answer<Boolean> getAnswerForDelete() {
+ return new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return true;
+ }
+ };
+ }
+
+}
diff --git a/app/src/test/resources/v4/ipv6_two_openvpn_one_pt_gateways.json b/app/src/test/resources/v4/ipv6_two_openvpn_one_pt_gateways.json
new file mode 100644
index 00000000..ad91581a
--- /dev/null
+++ b/app/src/test/resources/v4/ipv6_two_openvpn_one_pt_gateways.json
@@ -0,0 +1,123 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1194"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address":"37.218.247.60",
+ "ip_address6":"2001:db8:123::1058",
+ "location":"Amsterdam"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1194"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"moscow.bitmask.net",
+ "ip_address":"3.21.247.89",
+ "ip_address6":"2001:db8:123::1057",
+ "location":"moscow"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "udp",
+ "tcp"
+ ],
+ "ports":[
+ "1194"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"manila.bitmask.net",
+ "ip_address":"37.12.247.10",
+ "ip_address6":"2001:db8:123::1056",
+ "location":"manila"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ },
+ "moscow": {
+ "country_code": "RU",
+ "hemisphere": "N",
+ "name": "Moscow",
+ "timezone": "+3"
+ },
+ "manila": {
+ "country_code": "PH",
+ "hemisphere": "N",
+ "name": "Manila",
+ "timezone": "+8"
+ }
+ },
+ "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,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":4,
+ "version":4
+} \ No newline at end of file
diff --git a/app/src/test/resources/v4/ptdemo_pt_tcp_udp.eip-service.json b/app/src/test/resources/v4/ptdemo_pt_tcp_udp.eip-service.json
new file mode 100644
index 00000000..61a9f003
--- /dev/null
+++ b/app/src/test/resources/v4/ptdemo_pt_tcp_udp.eip-service.json
@@ -0,0 +1,66 @@
+{
+ "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",
+ "udp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address":"37.218.247.60",
+ "ip_address6":"2001:db8:123::1056",
+ "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":4,
+ "version":4
+} \ No newline at end of file
diff --git a/app/src/test/resources/v4/ptdemo_pt_udp_tcp.eip-service.json b/app/src/test/resources/v4/ptdemo_pt_udp_tcp.eip-service.json
new file mode 100644
index 00000000..456967cd
--- /dev/null
+++ b/app/src/test/resources/v4/ptdemo_pt_udp_tcp.eip-service.json
@@ -0,0 +1,66 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "udp",
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address":"37.218.247.60",
+ "ip_address6":"2001:db8:123::1056",
+ "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":4,
+ "version":4
+} \ No newline at end of file
diff --git a/app/src/test/resources/v4/riseup.geoip.json b/app/src/test/resources/v4/riseup.geoip.json
new file mode 100644
index 00000000..b646052e
--- /dev/null
+++ b/app/src/test/resources/v4/riseup.geoip.json
@@ -0,0 +1,20 @@
+{
+ "ip":"51.158.144.32",
+ "cc":"FR",
+ "city":"Paris",
+ "lat":48.8628,
+ "lon":2.3292,
+ "gateways":[
+ "mouette.riseup.net",
+ "hoatzin.riseup.net",
+ "zarapito.riseup.net",
+ "redshank.riseup.net",
+ "shag.riseup.net",
+ "yal.riseup.net",
+ "gaei.riseup.net",
+ "cisne.riseup.net",
+ "swan.riseup.net",
+ "garza.riseup.net",
+ "gaviota.riseup.net"
+ ]
+} \ No newline at end of file
diff --git a/app/src/test/resources/v4/riseup.net.json b/app/src/test/resources/v4/riseup.net.json
new file mode 100644
index 00000000..10e12df9
--- /dev/null
+++ b/app/src/test/resources/v4/riseup.net.json
@@ -0,0 +1,25 @@
+{
+ "api_uri": "https://api.black.riseup.net:443",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494",
+ "ca_cert_uri": "https://black.riseup.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change."
+ },
+ "domain": "riseup.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "Riseup Networks"
+ },
+ "service": {
+ "allow_anonymous": false,
+ "allow_registration": true
+ },
+ "services": [
+ "openvpn"
+ ]
+} \ No newline at end of file
diff --git a/app/src/test/resources/v4/riseup.service.json b/app/src/test/resources/v4/riseup.service.json
new file mode 100644
index 00000000..c27f8e18
--- /dev/null
+++ b/app/src/test/resources/v4/riseup.service.json
@@ -0,0 +1,96 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "ports":[
+ "443"
+ ],
+ "protocols":[
+ "tcp"
+ ],
+ "transport":[
+ "openvpn"
+ ],
+ "user_ips":false
+ },
+ "host":"garza.riseup.net",
+ "ip_address":"198.252.153.28",
+ "ip_address6":"2001:db8:123::1056",
+ "location":"seattle"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "ports":[
+ "443"
+ ],
+ "protocols":[
+ "tcp"
+ ],
+ "transport":[
+ "openvpn"
+ ],
+ "user_ips":false
+ },
+ "host":"tenca.riseup.net",
+ "ip_address":"5.79.86.180",
+ "ip_address6":"2001:db8:123::1056",
+ "location":"amsterdam"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "ports":[
+ "443"
+ ],
+ "protocols":[
+ "tcp"
+ ],
+ "transport":[
+ "openvpn"
+ ],
+ "user_ips":false
+ },
+ "host":"yal.riseup.net",
+ "ip_address":"199.58.81.145",
+ "ip_address6":"2001:db8:123::1056",
+ "location":"montreal"
+ }
+ ],
+ "locations":{
+ "amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"+2"
+ },
+ "montreal":{
+ "country_code":"CA",
+ "hemisphere":"N",
+ "name":"Montreal",
+ "timezone":"-5"
+ },
+ "seattle":{
+ "country_code":"US",
+ "hemisphere":"N",
+ "name":"Seattle",
+ "timezone":"-7"
+ }
+ },
+ "openvpn_configuration":{
+ "auth": "SHA512",
+ "cipher": "AES-128-CBC",
+ "keepalive": "10 30",
+ "tls-cipher": "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384",
+ "tun-ipv6": true
+ },
+ "serial":4,
+ "version":4
+} \ No newline at end of file