summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/eip
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/main/java/se/leap/bitmaskclient/eip
parentee1fe71f67d017af2d1695e438b28dea4a38cb38 (diff)
#8742 basic always-on implementation with blocking vpn if no profile is configured
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/eip')
-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
4 files changed, 244 insertions, 64 deletions
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);
+ }
+ }
+
}