summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2017-12-07 12:49:15 +0100
committercyBerta <cyberta@riseup.net>2017-12-07 12:49:15 +0100
commitca1cad6ec5b175a85b361c45e8d2c0cac0b405ec (patch)
treef057776613636469a06f3fe96dbc92c5658062ad /app/src
parentee1fe71f67d017af2d1695e438b28dea4a38cb38 (diff)
#8742 basic always-on implementation with blocking vpn if no profile is configured
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl2
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java1
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java20
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Dashboard.java134
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java43
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/VpnFragment.java142
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/Constants.java3
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EIP.java30
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java175
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java100
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java2
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java3
-rw-r--r--app/src/main/res/values/strings.xml5
13 files changed, 492 insertions, 168 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 3958bcf3..b19cf99e 100644
--- a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl
+++ b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl
@@ -20,4 +20,6 @@ interface IOpenVPNServiceInternal {
* @return true if there was a process that has been send a stop signal
*/
boolean stopVPN(boolean replaceConnection);
+
+ boolean isVpnRunning();
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java
index 03d842e3..3e6d23f7 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java
@@ -21,6 +21,7 @@ public enum ConnectionStatus implements Parcelable {
LEVEL_START,
LEVEL_AUTH_FAILED,
LEVEL_WAITING_FOR_USER_INPUT,
+ LEVEL_BLOCKING, // used for Bitmask's VoidVPN
UNKNOWN_LEVEL;
@Override
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 ded7490a..86e1bb02 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -115,6 +115,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
public boolean stopVPN(boolean replaceConnection) throws RemoteException {
return OpenVPNService.this.stopVPN(replaceConnection);
}
+
+ @Override
+ public boolean isVpnRunning() throws RemoteException {
+ return OpenVPNService.this.isVpnRunning();
+ }
};
// From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
@@ -412,6 +417,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
+ @Override
public void userPause(boolean shouldBePaused) {
if (mDeviceStateReceiver != null)
mDeviceStateReceiver.userPause(shouldBePaused);
@@ -425,6 +431,20 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
return false;
}
+ /**
+ * used in Bitmask
+ */
+ @Override
+ public boolean isVpnRunning() {
+ boolean hasVPNProcessThread = false;
+ synchronized (mProcessLock) {
+ hasVPNProcessThread = mProcessThread != null && mProcessThread.isAlive();
+ }
+
+ return hasVPNProcessThread;
+
+ }
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
index 3f1663d0..1a4adc1d 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
@@ -63,6 +63,9 @@ import se.leap.bitmaskclient.userstatus.SessionDialog;
import se.leap.bitmaskclient.userstatus.User;
import se.leap.bitmaskclient.userstatus.UserStatusFragment;
+import static se.leap.bitmaskclient.eip.Constants.IS_ALWAYS_ON;
+import static se.leap.bitmaskclient.eip.Constants.RESTART_ON_BOOT;
+
/**
* The main user facing Activity of Bitmask Android, consisting of status, controls,
* and access to preferences.
@@ -78,15 +81,23 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
public static final String TAG = Dashboard.class.getSimpleName();
public static final String SHARED_PREFERENCES = "LEAPPreferences";
public static final String ACTION_QUIT = "quit";
+
+ /**
+ * When "Disconnect" is clicked from the notification this extra gets added to the calling intent.
+ */
public static final String ACTION_ASK_TO_CANCEL_VPN = "ask to cancel vpn";
+ /**
+ * if always-on feature is enabled, but there's no provider configured the EIP Service
+ * adds this intent extra. ACTION_CONFIGURE_ALWAYS_ON_PROFILE
+ * serves to start the Configuration Wizard on top of the Dashboard Activity.
+ */
+ public static final String ACTION_CONFIGURE_ALWAYS_ON_PROFILE = "configure always-on profile";
public static final String REQUEST_CODE = "request_code";
public static final String PARAMETERS = "dashboard parameters";
- public static final String START_ON_BOOT = "dashboard start on boot";
- //FIXME: remove OR FIX ON_BOOT
- public static final String ON_BOOT = "dashboard on boot";
public static final String APP_VERSION = "bitmask version";
- private static Context app;
+ //FIXME: context classes in static fields lead to memory leaks!
+ private static Context dashboardContext;
protected static SharedPreferences preferences;
private FragmentManagerEnhanced fragment_manager;
@@ -108,31 +119,21 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
ProviderAPICommand.initialize(this);
providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), this);
- if (app == null) {
- app = this;
+ if (dashboardContext == null) {
+ dashboardContext = this;
VpnStatus.initLogCache(getApplicationContext().getCacheDir());
handleVersion();
User.init(getString(R.string.default_username));
}
- boolean provider_exists = previousProviderExists(savedInstanceState);
- if (provider_exists) {
- provider = getProvider(savedInstanceState);
- if(!provider.isConfigured())
- startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
- else {
- buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false));
- user_status_fragment.restoreSessionStatus(savedInstanceState);
- }
- } else {
- startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
- }
+
+ prepareEIP(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
- handleVPNCancellation(getIntent());
+ handleVpnCancellation(getIntent());
}
private boolean previousProviderExists(Bundle savedInstanceState) {
@@ -198,7 +199,19 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
- handleVPNCancellation(intent);
+ handleIntentExtras(intent);
+ }
+
+ private void handleIntentExtras(Intent intent) {
+ if (intent.hasExtra(ACTION_ASK_TO_CANCEL_VPN)) {
+ handleVpnCancellation(intent);
+ } else if (intent.hasExtra(RESTART_ON_BOOT)) {
+ Log.d(TAG, "Dashboard: RESTART_ON_BOOT");
+ prepareEIP(null);
+ } else if (intent.hasExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) {
+ Log.d(TAG, "Dashboard: ACTION_CONFIGURE_ALWAYS_ON_PROFILE");
+ handleConfigureAlwaysOn(getIntent());
+ }
}
@Override
@@ -221,15 +234,44 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
}
}
- private void handleVPNCancellation(Intent intent) {
+ private void handleVpnCancellation(Intent intent) {
if (intent.hasExtra(Dashboard.ACTION_ASK_TO_CANCEL_VPN)) {
eip_fragment.askToStopEIP();
intent.removeExtra(ACTION_ASK_TO_CANCEL_VPN);
}
}
+ private void handleConfigureAlwaysOn(Intent intent) {
+ intent.removeExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE);
+ Log.d(TAG, "start Configuration wizard!");
+ startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
+ }
+
+ private void prepareEIP(Bundle savedInstanceState) {
+ boolean provider_exists = previousProviderExists(savedInstanceState);
+ if (provider_exists) {
+ provider = getProvider(savedInstanceState);
+ if(!provider.isConfigured()) {
+ configureLeapProvider();
+ } else {
+ Log.d(TAG, "vpn provider is configured");
+ buildDashboard(getIntent().getBooleanExtra(RESTART_ON_BOOT, false));
+ user_status_fragment.restoreSessionStatus(savedInstanceState);
+ }
+ } else {
+ configureLeapProvider();
+ }
+ }
+
+ private void configureLeapProvider() {
+ if (getIntent().hasExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) {
+ getIntent().removeExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE);
+ }
+ startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
+ }
@SuppressLint("CommitPrefEdits")
private void providerToPreferences(Provider provider) {
+ //FIXME: figure out why .commit() is used and try refactor that cause, currently runs on UI thread
preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true).commit();
preferences.edit().putString(Provider.MAIN_URL, provider.mainUrl().toString()).apply();
preferences.edit().putString(Provider.KEY, provider.definition().toString()).apply();
@@ -261,7 +303,10 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
* Inflates permanent UI elements of the View and contains logic for what
* service dependent UI elements to include.
*/
- private void buildDashboard(boolean hide_and_turn_on_eip) {
+ //TODO: REFACTOR ME! Consider implementing a manager that handles most of VpnFragment's logic about handling EIP commands.
+ //This way, we could avoid to create UI elements (like fragment_manager.replace(R.id.servicesCollection, eip_fragment, VpnFragment.TAG); )
+ // just to start services and destroy them afterwards
+ private void buildDashboard(boolean hideAndTurnOnEipOnBoot) {
setContentView(R.layout.dashboard);
ButterKnife.inject(this);
@@ -275,24 +320,43 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
if (provider.hasEIP()) {
fragment_manager.removePreviousFragment(VpnFragment.TAG);
- eip_fragment = new VpnFragment();
-
- if (hide_and_turn_on_eip) {
- //TODO: remove line below if not in use anymore...
- preferences.edit().remove(Dashboard.START_ON_BOOT).apply();
- //FIXME: always start on Boot? Why do we keep shared preferences then?
- Bundle arguments = new Bundle();
- arguments.putBoolean(VpnFragment.START_ON_BOOT, true);
- if (eip_fragment != null) eip_fragment.setArguments(arguments);
- }
-
+ eip_fragment = prepareEipFragment(hideAndTurnOnEipOnBoot);
fragment_manager.replace(R.id.servicesCollection, eip_fragment, VpnFragment.TAG);
- if (hide_and_turn_on_eip) {
+ if (hideAndTurnOnEipOnBoot) {
onBackPressed();
}
}
}
+ /**
+ *
+ * @param hideAndTurnOnEipOnBoot Flag that indicates if system intent android.intent.action.BOOT_COMPLETED
+ * has caused to start Dashboard
+ * @return
+ */
+ private VpnFragment prepareEipFragment(boolean hideAndTurnOnEipOnBoot) {
+ VpnFragment eip_fragment = new VpnFragment();
+
+ if (hideAndTurnOnEipOnBoot && !isAlwaysOn()) {
+ preferences.edit().remove(Constants.RESTART_ON_BOOT).apply();
+ Bundle arguments = new Bundle();
+ arguments.putBoolean(VpnFragment.START_EIP_ON_BOOT, true);
+ Log.d(TAG, "set START_EIP_ON_BOOT argument for eip_fragment");
+ eip_fragment.setArguments(arguments);
+
+ }
+ return eip_fragment;
+ }
+
+ /**
+ * checks if Android's VPN feature 'always-on' is enabled for Bitmask
+ * @return
+ */
+ private boolean isAlwaysOn() {
+ return preferences.getBoolean(IS_ALWAYS_ON, false);
+ }
+
+
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (provider.allowsRegistration()) {
@@ -395,7 +459,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
}
public static Context getContext() {
- return app;
+ return dashboardContext;
}
public static Provider getProvider() { return provider; }
diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java
index 9171e816..943e877a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java
+++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java
@@ -1,25 +1,46 @@
package se.leap.bitmaskclient;
-import android.content.*;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
-import se.leap.bitmaskclient.eip.*;
+import static se.leap.bitmaskclient.eip.Constants.IS_ALWAYS_ON;
+import static se.leap.bitmaskclient.eip.Constants.RESTART_ON_BOOT;
+import static se.leap.bitmaskclient.eip.Constants.VPN_CERTIFICATE;
public class OnBootReceiver extends BroadcastReceiver {
SharedPreferences preferences;
- // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED
+
+ // Debug: su && am broadcast -a android.intent.action.BOOT_COMPLETED
@Override
public void onReceive(Context context, Intent intent) {
preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE);
- boolean provider_configured = !preferences.getString(Provider.KEY, "").isEmpty();
- boolean start_on_boot = preferences.getBoolean(Dashboard.START_ON_BOOT, false);
- if (provider_configured && start_on_boot) {
- Intent dashboard_intent = new Intent(context, Dashboard.class);
- dashboard_intent.setAction(Constants.ACTION_START_EIP);
- dashboard_intent.putExtra(Dashboard.ON_BOOT, true);
- dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(dashboard_intent);
+ boolean provider_configured = !preferences.getString(VPN_CERTIFICATE, "").isEmpty();
+ boolean start_on_boot = preferences.getBoolean(RESTART_ON_BOOT, false);
+ boolean isAlwaysOnConfigured = preferences.getBoolean(IS_ALWAYS_ON, false);
+ Log.d("OpenVPN", "OpenVPN onBoot intent received. Provider configured? " + provider_configured + " Start on boot? " + start_on_boot + " isAlwaysOn feature configured: " + isAlwaysOnConfigured);
+ if (provider_configured) {
+ if (isAlwaysOnConfigured) {
+ //exit because the app is already setting up the vpn
+ return;
+ }
+ if (start_on_boot) {
+ Intent dashboard_intent = new Intent(context, Dashboard.class);
+ dashboard_intent.putExtra(RESTART_ON_BOOT, true);
+ dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(dashboard_intent);
+ }
+ } else {
+ if (isAlwaysOnConfigured) {
+ Intent dashboard_intent = new Intent(context, Dashboard.class);
+ dashboard_intent.putExtra(Dashboard.ACTION_CONFIGURE_ALWAYS_ON_PROFILE, true);
+ dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(dashboard_intent);
+ }
}
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java
index c85b0151..57c066aa 100644
--- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java
@@ -16,24 +16,50 @@
*/
package se.leap.bitmaskclient;
-import android.app.*;
-import android.content.*;
-import android.os.*;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.util.Log;
-import android.view.*;
-import android.widget.*;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
-import org.jetbrains.annotations.*;
+import org.jetbrains.annotations.NotNull;
-import java.util.*;
+import java.util.Observable;
+import java.util.Observer;
-import butterknife.*;
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+import butterknife.OnClick;
+import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VpnStatus;
-import mbanje.kurt.fabbutton.*;
-import se.leap.bitmaskclient.eip.*;
+import mbanje.kurt.fabbutton.FabButton;
+import se.leap.bitmaskclient.eip.Constants;
+import se.leap.bitmaskclient.eip.EIP;
+import se.leap.bitmaskclient.eip.EipStatus;
+import se.leap.bitmaskclient.eip.VoidVpnService;
+
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
+import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.BLOCKING;
+import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTED;
+import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTING;
+import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTED;
+import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTING;
public class VpnFragment extends Fragment implements Observer {
@@ -41,7 +67,7 @@ public class VpnFragment extends Fragment implements Observer {
public static final String IS_PENDING = TAG + ".is_pending";
protected static final String IS_CONNECTED = TAG + ".is_connected";
- public static final String START_ON_BOOT = "start on boot";
+ public static final String START_EIP_ON_BOOT = "start on boot";
@InjectView(R.id.vpn_status_image)
FabButton vpn_status_image;
@@ -94,26 +120,19 @@ public class VpnFragment extends Fragment implements Observer {
ButterKnife.inject(this, view);
Bundle arguments = getArguments();
- if (arguments != null && arguments.containsKey(START_ON_BOOT) && arguments.getBoolean(START_ON_BOOT))
+ if (arguments != null && arguments.containsKey(START_EIP_ON_BOOT) && arguments.getBoolean(START_EIP_ON_BOOT)) {
startEipFromScratch();
- if (savedInstanceState != null) restoreState(savedInstanceState);
+ }
return view;
}
- private void restoreState(@NotNull Bundle savedInstanceState) {
- if (savedInstanceState.getBoolean(IS_PENDING))
- eip_status.setConnecting();
- else if (savedInstanceState.getBoolean(IS_CONNECTED))
- eip_status.setConnectedOrDisconnected();
- }
-
@Override
public void onResume() {
super.onResume();
//FIXME: avoid race conditions while checking certificate an logging in at about the same time
//eipCommand(Constants.ACTION_CHECK_CERT_VALIDITY);
- handleNewState(eip_status);
+ handleNewState();
bindOpenVpnService();
}
@@ -130,9 +149,9 @@ public class VpnFragment extends Fragment implements Observer {
super.onSaveInstanceState(outState);
}
- private void saveStatus() {
- boolean is_on = eip_status.isConnected() || eip_status.isConnecting();
- Dashboard.preferences.edit().putBoolean(Dashboard.START_ON_BOOT, is_on).commit();
+ private void saveStatus(boolean restartOnBoot) {
+ //boolean is_on = eip_status.isConnected() || eip_status.isConnecting() || eip_status.isBlocking();
+ Dashboard.preferences.edit().putBoolean(Constants.RESTART_ON_BOOT, restartOnBoot).commit();
}
@OnClick(R.id.vpn_main_button)
@@ -142,7 +161,7 @@ public class VpnFragment extends Fragment implements Observer {
else
handleSwitchOn();
- saveStatus();
+ saveStatus(eip_status.isConnected() || eip_status.isConnecting());
}
private void handleSwitchOn() {
@@ -177,8 +196,11 @@ public class VpnFragment extends Fragment implements Observer {
askPendingStartCancellation();
} else if (eip_status.isConnected()) {
askToStopEIP();
- } else
+ } else if (eip_status.isBlocking()) {
+ stop();
+ } else {
updateIcon();
+ }
}
private void askPendingStartCancellation() {
@@ -201,25 +223,26 @@ public class VpnFragment extends Fragment implements Observer {
public void startEipFromScratch() {
wants_to_connect = false;
- eip_status.setConnecting();
+ //eip_status.setEipLevel(BLOCKING);
- saveStatus();
+ saveStatus(true);
eipCommand(Constants.ACTION_START_EIP);
}
private void stop() {
- if (eip_status.isConnecting())
+
+ if (eip_status.isBlockingVpnEstablished()) {
+ Log.d(TAG, "stop VoidVpn!");
VoidVpnService.stop();
+ }
disconnect();
}
private void disconnect() {
- eip_status.setDisconnecting();
ProfileManager.setConntectedVpnProfileDisconnected(dashboard);
if (mService != null) {
try {
mService.stopVPN(false);
- eip_status.setConnectedOrDisconnected();
} catch (RemoteException e) {
VpnStatus.logException(e);
}
@@ -227,6 +250,7 @@ public class VpnFragment extends Fragment implements Observer {
}
protected void stopEipIfPossible() {
+ //FIXME: no need to start a service here!
eipCommand(Constants.ACTION_STOP_EIP);
}
@@ -276,36 +300,33 @@ public class VpnFragment extends Fragment implements Observer {
public void update(Observable observable, Object data) {
if (observable instanceof EipStatus) {
eip_status = (EipStatus) observable;
- final EipStatus eip_status = (EipStatus) observable;
dashboard.runOnUiThread(new Runnable() {
@Override
public void run() {
- handleNewState(eip_status);
+ handleNewState();
}
});
}
}
- private void handleNewState(EipStatus eip_status) {
- Context context = dashboard.getApplicationContext();
- String error = eip_status.lastError(5, context);
-
- if (!error.isEmpty()) VoidVpnService.stop();
+ private void handleNewState() {
updateIcon();
updateButton();
}
private void updateIcon() {
- if (eip_status.isConnected() || eip_status.isConnecting()) {
- if(eip_status.isConnecting()) {
- vpn_status_image.showProgress(true);
- vpn_status_image.setIcon(R.drawable.ic_stat_vpn_empty_halo, R.drawable.ic_stat_vpn_empty_halo);
- vpn_status_image.setTag(R.drawable.ic_stat_vpn_empty_halo);
- } else {
- vpn_status_image.showProgress(false);
- vpn_status_image.setIcon(R.drawable.ic_stat_vpn, R.drawable.ic_stat_vpn);
- vpn_status_image.setTag(R.drawable.ic_stat_vpn);
- }
+ if (eip_status.isBlocking()) {
+ vpn_status_image.showProgress(false);
+ vpn_status_image.setIcon(R.drawable.ic_stat_vpn_blocking, R.drawable.ic_stat_vpn_blocking);
+ vpn_status_image.setTag(R.drawable.ic_stat_vpn_blocking);
+ } else if (eip_status.isConnecting()) {
+ vpn_status_image.showProgress(true);
+ vpn_status_image.setIcon(R.drawable.ic_stat_vpn_empty_halo, R.drawable.ic_stat_vpn_empty_halo);
+ vpn_status_image.setTag(R.drawable.ic_stat_vpn_empty_halo);
+ } else if (eip_status.isConnected()){
+ vpn_status_image.showProgress(false);
+ vpn_status_image.setIcon(R.drawable.ic_stat_vpn, R.drawable.ic_stat_vpn);
+ vpn_status_image.setTag(R.drawable.ic_stat_vpn);
} else {
vpn_status_image.setIcon(R.drawable.ic_stat_vpn_offline, R.drawable.ic_stat_vpn_offline);
vpn_status_image.setTag(R.drawable.ic_stat_vpn_offline);
@@ -314,17 +335,28 @@ public class VpnFragment extends Fragment implements Observer {
}
private void updateButton() {
- if (eip_status.isConnected() || eip_status.isConnecting()) {
- if(eip_status.isConnecting()) {
- main_button.setText(dashboard.getString(android.R.string.cancel));
- } else {
- main_button.setText(dashboard.getString(R.string.vpn_button_turn_off));
- }
+ if (eip_status.isConnecting()) {
+ main_button.setText(dashboard.getString(android.R.string.cancel));
+ } else if (eip_status.isConnected() || isOpenVpnRunningWithoutNetwork()) {
+ main_button.setText(dashboard.getString(R.string.vpn_button_turn_off));
} else {
main_button.setText(dashboard.getString(R.string.vpn_button_turn_on));
}
}
+ private boolean isOpenVpnRunningWithoutNetwork() {
+ boolean isRunning = false;
+ try {
+ isRunning = eip_status.getLevel() == LEVEL_NONETWORK &&
+ mService.isVpnRunning();
+ } catch (Exception e) {
+ //eat me
+ e.printStackTrace();
+ }
+
+ return isRunning;
+ }
+
private void bindOpenVpnService() {
Intent intent = new Intent(dashboard, OpenVPNService.class);
intent.setAction(OpenVPNService.START_SERVICE);
@@ -381,7 +413,7 @@ public class VpnFragment extends Fragment implements Observer {
startEipFromScratch();
break;
case Activity.RESULT_CANCELED:
- handleNewState(eip_status);
+ handleNewState();
break;
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java
index db1cb4a1..ed4ebcbc 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java
@@ -27,6 +27,7 @@ public interface Constants {
public final static String ACTION_CHECK_CERT_VALIDITY = TAG + ".CHECK_CERT_VALIDITY";
public final static String ACTION_START_EIP = TAG + ".START_EIP";
+ public final static String ACTION_START_ALWAYS_ON_EIP = TAG + ".START_ALWAYS_ON_EIP";
public final static String ACTION_STOP_EIP = TAG + ".STOP_EIP";
public final static String ACTION_UPDATE_EIP_SERVICE = TAG + ".UPDATE_EIP_SERVICE";
public final static String ACTION_IS_EIP_RUNNING = TAG + ".IS_RUNNING";
@@ -40,5 +41,7 @@ public interface Constants {
public final static String REQUEST_TAG = TAG + ".REQUEST_TAG";
public final static String START_BLOCKING_VPN_PROFILE = TAG + ".START_BLOCKING_VPN_PROFILE";
public final static String PROVIDER_CONFIGURED = TAG + ".PROVIDER_CONFIGURED";
+ public final static String IS_ALWAYS_ON = TAG + ".IS_ALWAYS_ON";
+ public final static String RESTART_ON_BOOT = TAG + ".RESTART_ON_BOOT";
}
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 28a9bb50..39dd133f 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
@@ -19,6 +19,7 @@ package se.leap.bitmaskclient.eip;
import android.app.*;
import android.content.*;
import android.os.*;
+import android.util.Log;
import org.json.*;
@@ -57,7 +58,6 @@ public final class EIP extends IntentService {
@Override
public void onCreate() {
super.onCreate();
-
context = getApplicationContext();
preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE);
eip_definition = eipDefinitionFromPreferences();
@@ -72,6 +72,8 @@ public final class EIP extends IntentService {
if (action.equals(ACTION_START_EIP))
startEIP();
+ else if (action.equals(ACTION_START_ALWAYS_ON_EIP))
+ startAlwaysOnEIP();
else if (action.equals(ACTION_STOP_EIP))
stopEIP();
else if (action.equals(ACTION_IS_EIP_RUNNING))
@@ -88,9 +90,12 @@ public final class EIP extends IntentService {
* It also sets up early routes.
*/
private void startEIP() {
+ Log.d(TAG, "startEIP vpn");
if (gateways_manager.isEmpty())
updateEIPService();
- earlyRoutes();
+ if (!EipStatus.getInstance().isBlockingVpnEstablished()) {
+ earlyRoutes();
+ }
gateway = gateways_manager.select();
if (gateway != null && gateway.getProfile() != null) {
@@ -102,6 +107,27 @@ public final class EIP extends IntentService {
}
/**
+ * Tries to start the last used vpn profile when the OS was rebooted and always-on-VPN is enabled.
+ * The {@link OnBootReceiver} will care if there is no profile.
+ */
+ private void startAlwaysOnEIP() {
+ Log.d(TAG, "startAlwaysOnEIP vpn");
+
+ if (gateways_manager.isEmpty())
+ updateEIPService();
+
+ gateway = gateways_manager.select();
+
+ if (gateway != null && gateway.getProfile() != null) {
+ //mReceiver = VpnFragment.getReceiver();
+ Log.d(TAG, "startAlwaysOnEIP eip launch avtive gateway vpn");
+ launchActiveGateway();
+ } else {
+ Log.d(TAG, "startAlwaysOnEIP no active profile available!");
+ }
+ }
+
+ /**
* Early routes are routes that block traffic until a new
* VpnService is started properly.
*/
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 501543b8..dc2e81f5 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
@@ -17,19 +17,37 @@
package se.leap.bitmaskclient.eip;
import android.content.*;
+import android.os.AsyncTask;
+import android.support.annotation.VisibleForTesting;
import java.util.*;
import de.blinkt.openvpn.core.*;
+/**
+ * EipStatus is a Singleton that represents a reduced set of a vpn's ConnectionStatus.
+ * EipStatus changes it's state (EipLevel) when ConnectionStatus gets updated by OpenVpnService or
+ * by VoidVpnService.
+ */
public class EipStatus extends Observable implements VpnStatus.StateListener {
public static String TAG = EipStatus.class.getSimpleName();
private static EipStatus current_status;
+ public enum EipLevel {
+ CONNECTING,
+ DISCONNECTING,
+ CONNECTED,
+ DISCONNECTED,
+ BLOCKING,
+ UNKNOWN
+ }
- private static ConnectionStatus level = ConnectionStatus.LEVEL_NOTCONNECTED;
- private static boolean
- wants_to_disconnect = false,
- is_connecting = false;
+ /**
+ * vpn_level holds the connection status of the openvpn vpn and the traffic blocking
+ * void vpn. LEVEL_BLOCKING is set when the latter vpn is up. All other states are set by
+ * openvpn.
+ */
+ private ConnectionStatus vpn_level = ConnectionStatus.LEVEL_NOTCONNECTED;
+ private EipLevel current_eip_level = EipLevel.DISCONNECTED;
int last_error_line = 0;
private String state, log_message;
@@ -48,64 +66,137 @@ public class EipStatus extends Observable implements VpnStatus.StateListener {
@Override
public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) {
- updateStatus(state, logmessage, localizedResId, level);
- if (isConnected() || isDisconnected() || wantsToDisconnect()) {
- setConnectedOrDisconnected();
- } else
- setConnecting();
+ current_status = getInstance();
+ current_status.setState(state);
+ current_status.setLogMessage(logmessage);
+ current_status.setLocalizedResId(localizedResId);
+ current_status.setLevel(level);
+ current_status.setEipLevel(level);
}
@Override
public void setConnectedVPN(String uuid) {
}
- private void updateStatus(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) {
- current_status = getInstance();
- current_status.setState(state);
- current_status.setLogMessage(logmessage);
- current_status.setLocalizedResId(localizedResId);
- current_status.setLevel(level);
- current_status.setChanged();
+
+ private void setEipLevel(ConnectionStatus level) {
+ EipLevel tmp = current_eip_level;
+ switch (level) {
+ case LEVEL_CONNECTED:
+ current_eip_level = EipLevel.CONNECTED;
+ break;
+ case LEVEL_VPNPAUSED:
+ throw new IllegalStateException("Ics-Openvpn's VPNPAUSED state is not supported by Bitmask");
+ case LEVEL_CONNECTING_SERVER_REPLIED:
+ case LEVEL_CONNECTING_NO_SERVER_REPLY_YET:
+ case LEVEL_WAITING_FOR_USER_INPUT:
+ case LEVEL_START:
+ current_eip_level = EipLevel.CONNECTING;
+ break;
+ case LEVEL_AUTH_FAILED:
+ case LEVEL_NOTCONNECTED:
+ current_eip_level = EipLevel.DISCONNECTED;
+ break;
+ case LEVEL_NONETWORK:
+ case LEVEL_BLOCKING:
+ setEipLevelWithDelay(level);
+ break;
+ case UNKNOWN_LEVEL:
+ current_eip_level = EipLevel.UNKNOWN; //??
+ break;
+ }
+ if (tmp != current_eip_level) {
+ current_status.setChanged();
+ current_status.notifyObservers();
+ }
}
- public boolean wantsToDisconnect() {
- return wants_to_disconnect;
+ @VisibleForTesting
+ EipLevel getEipLevel() {
+ return current_eip_level;
}
- public boolean isConnecting() {
- return is_connecting;
+ /**
+ * This method intends to ignore states that are valid for less than a second.
+ * This way flickering UI changes can be avoided
+ *
+ * @param futureLevel
+ */
+ private void setEipLevelWithDelay(ConnectionStatus futureLevel) {
+ new DelayTask(current_status.getLevel(), futureLevel).execute();
}
- public boolean isConnected() {
- return level == ConnectionStatus.LEVEL_CONNECTED;
+ private class DelayTask extends AsyncTask<Void, Void, Void> {
+
+ private final ConnectionStatus currentLevel;
+ private final ConnectionStatus futureLevel;
+
+ public DelayTask(ConnectionStatus currentLevel, ConnectionStatus futureLevel) {
+ this.currentLevel = currentLevel;
+ this.futureLevel = futureLevel;
+ }
+ protected Void doInBackground(Void... levels) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.interrupted();
+ }
+ return null;
+ }
+
+ protected void onPostExecute(Void result) {;
+ if (currentLevel == current_status.getLevel()) {
+ switch (futureLevel) {
+ case LEVEL_NONETWORK:
+ current_eip_level = EipLevel.DISCONNECTED;
+ break;
+ case LEVEL_BLOCKING:
+ current_eip_level = EipLevel.BLOCKING;
+ break;
+ default:
+ break;
+ }
+ current_status.setChanged();
+ current_status.notifyObservers();
+ }
+ }
}
- public boolean isDisconnected() {
- return level == ConnectionStatus.LEVEL_NOTCONNECTED;
+ public boolean isConnecting() {
+ return current_eip_level == EipLevel.CONNECTING;
}
- public boolean isPaused() {
- return level == ConnectionStatus.LEVEL_VPNPAUSED;
+ public boolean isConnected() {
+ return current_eip_level == EipLevel.CONNECTED;
}
- public void setConnecting() {
- is_connecting = true;
+ /**
+ * @return true if current_eip_level is for at least a second {@link EipLevel#BLOCKING}.
+ * See {@link #setEipLevelWithDelay(ConnectionStatus)}.
+ */
+ public boolean isBlocking() {
+ return current_eip_level == EipLevel.BLOCKING;
+ }
- wants_to_disconnect = false;
- current_status.setChanged();
- current_status.notifyObservers();
+ /**
+ *
+ * @return true immediately after traffic blocking VoidVpn was established.
+ */
+ public boolean isBlockingVpnEstablished() {
+ return vpn_level == ConnectionStatus.LEVEL_BLOCKING;
}
- public void setConnectedOrDisconnected() {
- is_connecting = false;
- wants_to_disconnect = false;
- current_status.setChanged();
- current_status.notifyObservers();
+ public boolean isDisconnected() {
+ return current_eip_level == EipLevel.DISCONNECTED;
}
- public void setDisconnecting() {
- wants_to_disconnect = true;
- is_connecting = false;
+ /**
+ * ics-openvpn's paused state is not implemented yet
+ * @return
+ */
+ @Deprecated
+ public boolean isPaused() {
+ return vpn_level == ConnectionStatus.LEVEL_VPNPAUSED;
}
public String getState() {
@@ -121,7 +212,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener {
}
public ConnectionStatus getLevel() {
- return level;
+ return vpn_level;
}
private void setState(String state) {
@@ -137,7 +228,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener {
}
private void setLevel(ConnectionStatus level) {
- EipStatus.level = level;
+ this.vpn_level = level;
}
public boolean errorInLast(int lines, Context context) {
@@ -169,7 +260,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener {
@Override
public String toString() {
- return "State: " + state + " Level: " + level.toString();
+ return "State: " + state + " Level: " + vpn_level.toString();
}
}
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 cbf0fed2..46c010ca 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
@@ -1,41 +1,58 @@
package se.leap.bitmaskclient.eip;
-import android.content.*;
-import android.net.*;
-import android.os.*;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.VpnService;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+
+import de.blinkt.openvpn.core.ConnectionStatus;
+import de.blinkt.openvpn.core.VpnStatus;
+import se.leap.bitmaskclient.R;
+
+import static se.leap.bitmaskclient.Dashboard.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.eip.Constants.ACTION_START_ALWAYS_ON_EIP;
-import java.io.*;
public class VoidVpnService extends VpnService {
static final String TAG = VoidVpnService.class.getSimpleName();
static ParcelFileDescriptor fd;
-
static Thread thread;
+ private final int ALWAYS_ON_MIN_API_LEVEL = Build.VERSION_CODES.N;
+ private static final String STATE_ESTABLISH = "ESTABLISHVOIDVPN";
+ private static final String STATE_STOP = "STOPVOIDVPN";
@Override
- public int onStartCommand(Intent intent, int flags, int startId) {
+ public int onStartCommand(final Intent intent, int flags, int startId) {
String action = intent != null ? intent.getAction() : "";
- if (action == Constants.START_BLOCKING_VPN_PROFILE) {
+ if (action.equals(Constants.START_BLOCKING_VPN_PROFILE)) {
thread = new Thread(new Runnable() {
public void run() {
- Builder builder = new Builder();
- builder.setSession("Blocking until running");
- builder.addAddress("10.42.0.8", 16);
- builder.addRoute("0.0.0.0", 1);
- builder.addRoute("192.168.1.0", 24);
- builder.addDnsServer("10.42.0.1");
- try {
- fd = builder.establish();
-
- } catch (Exception e) {
- e.printStackTrace();
- }
+ establishBlockingVpn();
+ SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ preferences.edit().putBoolean(Constants.IS_ALWAYS_ON, false).commit();
+ Log.d(TAG, "start blocking vpn profile - always on = false");
+ }
+ });
+ thread.run();
+ } else if (action.equals("android.net.VpnService") && Build.VERSION.SDK_INT >= ALWAYS_ON_MIN_API_LEVEL) {
+ //only always-on feature triggers this
+ thread = new Thread(new Runnable() {
+ public void run() {
+ establishBlockingVpn();
+ SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ preferences.edit().putBoolean(Constants.IS_ALWAYS_ON, true).commit();
+ requestVpnWithLastSelectedProfile();
+ Log.d(TAG, "start blocking vpn profile - always on = true");
}
});
thread.run();
}
- return 0;
+ return START_STICKY;
}
@Override
@@ -48,6 +65,8 @@ public class VoidVpnService extends VpnService {
if (thread != null)
thread.interrupt();
closeFd();
+ VpnStatus.updateStateString(STATE_STOP, "",
+ R.string.void_vpn_stopped, ConnectionStatus.LEVEL_NOTCONNECTED);
}
public static boolean isRunning() throws NullPointerException {
@@ -62,4 +81,45 @@ public class VoidVpnService extends VpnService {
e.printStackTrace();
}
}
+
+ private Builder prepareBlockingVpnProfile() {
+ Builder builder = new Builder();
+ builder.setSession("Blocking until running");
+ builder.addRoute("0.0.0.0", 1);
+ builder.addRoute("192.168.1.0", 24);
+ builder.addDnsServer("10.42.0.1");
+ builder.addAddress("10.42.0.8", 16);
+ return builder;
+
+ }
+
+ private void establishBlockingVpn() {
+ try {
+ VpnStatus.logInfo(getString(R.string.void_vpn_establish));
+ VpnStatus.updateStateString(STATE_ESTABLISH, "",
+ R.string.void_vpn_establish, ConnectionStatus.LEVEL_BLOCKING);
+ Builder builder = prepareBlockingVpnProfile();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ builder.addDisallowedApplication(getPackageName());
+ }
+
+ fd = builder.establish();
+ } catch (Exception e) {
+ // Catch any exception
+ e.printStackTrace();
+ VpnStatus.logError(R.string.void_vpn_error_establish);
+ }
+ }
+
+ private void requestVpnWithLastSelectedProfile() {
+ Intent startEIP = new Intent(getApplicationContext(), EIP.class);
+ startEIP.setAction(ACTION_START_ALWAYS_ON_EIP);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ //noinspection NewApi
+ getApplicationContext().startForegroundService(startEIP);
+ } else {
+ getApplicationContext().startService(startEIP);
+ }
+ }
+
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java
index d124c395..37744927 100644
--- a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java
@@ -159,7 +159,7 @@ public class SessionDialog extends DialogFragment {
public void logIn(String username, String password);
public void signUp(String username, String password);
-
+ //FIXME: can we remove this method?
public void cancelLoginOrSignup();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java
index 20189904..14323f8e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java
@@ -24,7 +24,6 @@ import se.leap.bitmaskclient.ProviderAPI;
import se.leap.bitmaskclient.ProviderAPICommand;
import se.leap.bitmaskclient.ProviderAPIResultReceiver;
import se.leap.bitmaskclient.R;
-import se.leap.bitmaskclient.eip.EipStatus;
public class UserStatusFragment extends Fragment implements Observer, SessionDialog.SessionDialogInterface {
@@ -169,7 +168,7 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia
}
public void cancelLoginOrSignup() {
- EipStatus.getInstance().setConnectedOrDisconnected();
+ //EipStatus.getInstance().setConnectedOrDisconnected();
}
private Bundle bundlePassword(String password) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 04e83a18..70f6f4ab 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -76,5 +76,10 @@
<string name="signingup_message">is being registered.</string>
<string name="vpn.button.turn.on">Turn on</string>
<string name="vpn.button.turn.off">Turn off</string>
+ <string name="vpn_button_turn_off_blocking">Stop blocking</string>
<string name="bitmask_log">Bitmask Log</string>
+ <string name="void_vpn_establish">Bitmask blocks all outgoing internet traffic.</string>
+ <string name="void_vpn_error_establish">Failed to establish blocking VPN.</string>
+ <string name="void_vpn_stopped">Stopped blocking all outgoing internet traffic.</string>
+ <string name="void_vpn_title">Blocking traffic</string>
</resources>