summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-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
17 files changed, 560 insertions, 434 deletions
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);