summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-12-04 09:38:41 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-12-04 09:38:41 -0300
commit6e9d2c96c9fbd13f06d3d1ab2f4b36a55995d3ae (patch)
treeeb2779084909cdc1a07b70f5178ad7fd741aa0ff
parent09cd80e4b818ab66876b059c94ecc169feccd0dc (diff)
parent9c5a7afff17913d8adeff3ea3fa2c972bfa9b00b (diff)
downloadmullvadvpn-6e9d2c96c9fbd13f06d3d1ab2f4b36a55995d3ae.tar.xz
mullvadvpn-6e9d2c96c9fbd13f06d3d1ab2f4b36a55995d3ae.zip
Merge branch 'android-offline-check'
-rw-r--r--CHANGELOG.md3
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt2
-rw-r--r--android/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt68
-rw-r--r--android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt10
-rw-r--r--mullvad-jni/src/classes.rs1
-rw-r--r--talpid-core/src/offline/android.rs217
-rw-r--r--talpid-core/src/offline/dummy.rs21
-rw-r--r--talpid-core/src/offline/mod.rs17
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs8
-rw-r--r--talpid-types/src/android/mod.rs13
10 files changed, 333 insertions, 27 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index afb065c704..06a9ff8220 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,9 @@ Line wrap the file at 100 chars. Th
- Remove Mullvad TAP adapter on uninstall. Also remove the TAP driver if there are no other TAP
adapters in the system.
+#### Android
+- Add connectivity status check.
+
### Changed
- Notifications shown when connecting to a server include its location.
- Upgrade OpenVPN from 2.4.7 to 2.4.8.
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
index 67fe711a6c..91bc269ba2 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
@@ -23,6 +23,7 @@ class MullvadVpnService : TalpidVpnService() {
private lateinit var notificationManager: ForegroundNotificationManager
override fun onCreate() {
+ super.onCreate()
setUp()
created.complete(Unit)
}
@@ -47,6 +48,7 @@ class MullvadVpnService : TalpidVpnService() {
tearDown()
daemon.cancel()
created.cancel()
+ super.onDestroy()
}
inner class LocalBinder : Binder() {
diff --git a/android/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt b/android/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt
new file mode 100644
index 0000000000..f0b6e2e923
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt
@@ -0,0 +1,68 @@
+package net.mullvad.talpid
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.NetworkInfo
+import android.net.NetworkInfo.DetailedState
+
+class ConnectivityListener : BroadcastReceiver() {
+ var isConnected = true
+ private set(value) {
+ field = value
+
+ if (senderAddress != 0L) {
+ notifyConnectivityChange(value, senderAddress)
+ }
+ }
+
+ var senderAddress = 0L
+
+ fun register(context: Context) {
+ val intentFilter = IntentFilter()
+
+ intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
+ context.registerReceiver(this, intentFilter)
+
+ checkConnectionState(context)
+ }
+
+ fun unregister(context: Context) {
+ context.unregisterReceiver(this)
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val networkInfo =
+ intent.getParcelableExtra<NetworkInfo>(ConnectivityManager.EXTRA_NETWORK_INFO)
+
+ if (networkInfo.type != ConnectivityManager.TYPE_VPN) {
+ if (networkInfo.detailedState == DetailedState.DISCONNECTED) {
+ checkConnectionState(context)
+ } else if (networkInfo.detailedState == DetailedState.CONNECTED) {
+ isConnected = true
+ }
+ }
+ }
+
+ private fun checkConnectionState(context: Context) {
+ val connectivityManager =
+ context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+ isConnected = connectivityManager.allNetworks
+ .map({ network -> connectivityManager.getNetworkInfo(network) })
+ .any({ networkInfo ->
+ networkInfo.type != ConnectivityManager.TYPE_VPN &&
+ networkInfo.detailedState == DetailedState.CONNECTED
+ })
+ }
+
+ private fun finalize() {
+ destroySender(senderAddress)
+ senderAddress = 0L
+ }
+
+ private external fun notifyConnectivityChange(isConnected: Boolean, senderAddress: Long)
+ private external fun destroySender(senderAddress: Long)
+}
diff --git a/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt b/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
index 96de4082cc..185f401f7e 100644
--- a/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
@@ -4,6 +4,16 @@ import android.net.VpnService
import net.mullvad.talpid.tun_provider.TunConfig
open class TalpidVpnService : VpnService() {
+ val connectivityListener = ConnectivityListener()
+
+ override fun onCreate() {
+ connectivityListener.register(this)
+ }
+
+ override fun onDestroy() {
+ connectivityListener.unregister(this)
+ }
+
fun createTun(config: TunConfig): Int {
val builder = Builder().apply {
for (address in config.addresses) {
diff --git a/mullvad-jni/src/classes.rs b/mullvad-jni/src/classes.rs
index 17bbe99522..d2e37b0804 100644
--- a/mullvad-jni/src/classes.rs
+++ b/mullvad-jni/src/classes.rs
@@ -53,5 +53,6 @@ pub const CLASSES: &[&str] = &[
"net/mullvad/talpid/tunnel/BlockReason$IsOffline",
"net/mullvad/talpid/tunnel/BlockReason$TapAdapterProblem",
"net/mullvad/talpid/tunnel/ParameterGenerationError",
+ "net/mullvad/talpid/ConnectivityListener",
"net/mullvad/talpid/TalpidVpnService",
];
diff --git a/talpid-core/src/offline/android.rs b/talpid-core/src/offline/android.rs
new file mode 100644
index 0000000000..eb33331314
--- /dev/null
+++ b/talpid-core/src/offline/android.rs
@@ -0,0 +1,217 @@
+use crate::tunnel_state_machine::TunnelCommand;
+use futures::sync::mpsc::UnboundedSender;
+use jnix::{
+ jni::{
+ self,
+ objects::{GlobalRef, JObject, JValue},
+ signature::{JavaType, Primitive},
+ sys::{jboolean, jlong, JNI_FALSE},
+ JNIEnv, JavaVM,
+ },
+ JnixEnv,
+};
+use std::sync::Weak;
+use talpid_types::{android::AndroidContext, ErrorExt};
+
+#[derive(err_derive::Error, Debug)]
+#[error(no_from)]
+pub enum Error {
+ #[error(display = "Failed to attach Java VM to tunnel thread")]
+ AttachJvmToThread(#[error(source)] jni::errors::Error),
+
+ #[error(display = "Failed to call Java method {}.{}", _0, _1)]
+ CallMethod(
+ &'static str,
+ &'static str,
+ #[error(source)] jni::errors::Error,
+ ),
+
+ #[error(display = "Failed to create global reference to Java object")]
+ CreateGlobalRef(#[error(source)] jni::errors::Error),
+
+ #[error(display = "Failed to find {}.{} method", _0, _1)]
+ FindMethod(
+ &'static str,
+ &'static str,
+ #[error(source)] jni::errors::Error,
+ ),
+
+ #[error(display = "Received an invalid result from {}.{}: {}", _0, _1, _2)]
+ InvalidMethodResult(&'static str, &'static str, String),
+}
+
+pub struct MonitorHandle {
+ jvm: JavaVM,
+ class: GlobalRef,
+ object: GlobalRef,
+}
+
+impl MonitorHandle {
+ pub fn new(android_context: AndroidContext) -> Result<Self, Error> {
+ let env = JnixEnv::from(
+ android_context
+ .jvm
+ .attach_current_thread_as_daemon()
+ .map_err(Error::AttachJvmToThread)?,
+ );
+
+ let get_connectivity_listener_method = env
+ .get_method_id(
+ &env.get_class("net/mullvad/talpid/TalpidVpnService"),
+ "getConnectivityListener",
+ "()Lnet/mullvad/talpid/ConnectivityListener;",
+ )
+ .map_err(|cause| {
+ Error::FindMethod("MullvadVpnService", "getConnectivityListener", cause)
+ })?;
+
+ let result = env
+ .call_method_unchecked(
+ android_context.vpn_service.as_obj(),
+ get_connectivity_listener_method,
+ JavaType::Object("Lnet/mullvad/talpid/ConnectivityListener;".to_owned()),
+ &[],
+ )
+ .map_err(|cause| {
+ Error::CallMethod("MullvadVpnService", "getConnectivityListener", cause)
+ })?;
+
+ let object = match result {
+ JValue::Object(object) => env.new_global_ref(object).map_err(Error::CreateGlobalRef)?,
+ value => {
+ return Err(Error::InvalidMethodResult(
+ "MullvadVpnService",
+ "getConnectivityListener",
+ format!("{:?}", value),
+ ))
+ }
+ };
+
+ let class = env.get_class("net/mullvad/talpid/ConnectivityListener");
+
+ Ok(MonitorHandle {
+ jvm: android_context.jvm,
+ class,
+ object,
+ })
+ }
+
+ pub fn is_offline(&self) -> bool {
+ match self.get_is_connected() {
+ Ok(is_connected) => !is_connected,
+ Err(error) => {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to check connectivity status")
+ );
+ false
+ }
+ }
+ }
+
+ fn get_is_connected(&self) -> Result<bool, Error> {
+ let result = self.call_method(
+ "isConnected",
+ "()Z",
+ &[],
+ JavaType::Primitive(Primitive::Boolean),
+ )?;
+
+ match result {
+ JValue::Bool(JNI_FALSE) => Ok(false),
+ JValue::Bool(_) => Ok(true),
+ value => Err(Error::InvalidMethodResult(
+ "ConnectivityListener",
+ "isConnected",
+ format!("{:?}", value),
+ )),
+ }
+ }
+
+ fn set_sender(&self, sender: Weak<UnboundedSender<TunnelCommand>>) -> Result<(), Error> {
+ let sender_ptr = Box::new(sender);
+ let sender_address = Box::into_raw(sender_ptr) as jlong;
+
+ let result = self.call_method(
+ "setSenderAddress",
+ "(J)V",
+ &[JValue::Long(sender_address)],
+ JavaType::Primitive(Primitive::Void),
+ )?;
+
+ match result {
+ JValue::Void => Ok(()),
+ value => Err(Error::InvalidMethodResult(
+ "ConnectivityListener",
+ "setSenderAddress",
+ format!("{:?}", value),
+ )),
+ }
+ }
+
+ fn call_method(
+ &self,
+ method: &'static str,
+ signature: &str,
+ parameters: &[JValue<'_>],
+ return_type: JavaType,
+ ) -> Result<JValue<'_>, Error> {
+ let env = JnixEnv::from(
+ self.jvm
+ .attach_current_thread_as_daemon()
+ .map_err(Error::AttachJvmToThread)?,
+ );
+
+ let method_id = env
+ .get_method_id(&self.class, method, signature)
+ .map_err(|cause| Error::FindMethod("ConnectivityListener", method, cause))?;
+
+ env.call_method_unchecked(self.object.as_obj(), method_id, return_type, parameters)
+ .map_err(|cause| Error::CallMethod("ConnectivityListener", method, cause))
+ }
+}
+
+/// Entry point for Android Java code to notify the connectivity status.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "system" fn Java_net_mullvad_talpid_ConnectivityListener_notifyConnectivityChange(
+ _: JNIEnv<'_>,
+ _: JObject<'_>,
+ is_connected: jboolean,
+ sender_address: jlong,
+) {
+ let sender_ref = Box::leak(unsafe { get_sender_from_address(sender_address) });
+ let tunnel_command = TunnelCommand::IsOffline(is_connected == JNI_FALSE);
+
+ if let Some(sender) = sender_ref.upgrade() {
+ if sender.unbounded_send(tunnel_command).is_err() {
+ log::warn!("Failed to send offline change event");
+ }
+ }
+}
+
+/// Entry point for Android Java code to return ownership of the sender reference.
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "system" fn Java_net_mullvad_talpid_ConnectivityListener_destroySender(
+ _: JNIEnv<'_>,
+ _: JObject<'_>,
+ sender_address: jlong,
+) {
+ let _ = unsafe { get_sender_from_address(sender_address) };
+}
+
+unsafe fn get_sender_from_address(address: jlong) -> Box<Weak<UnboundedSender<TunnelCommand>>> {
+ Box::from_raw(address as *mut Weak<UnboundedSender<TunnelCommand>>)
+}
+
+pub fn spawn_monitor(
+ sender: Weak<UnboundedSender<TunnelCommand>>,
+ android_context: AndroidContext,
+) -> Result<MonitorHandle, Error> {
+ let monitor_handle = MonitorHandle::new(android_context)?;
+
+ monitor_handle.set_sender(sender)?;
+
+ Ok(monitor_handle)
+}
diff --git a/talpid-core/src/offline/dummy.rs b/talpid-core/src/offline/dummy.rs
deleted file mode 100644
index 7eda41d433..0000000000
--- a/talpid-core/src/offline/dummy.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use crate::tunnel_state_machine::TunnelCommand;
-use futures::sync::mpsc::UnboundedSender;
-use std::sync::Weak;
-
-#[derive(err_derive::Error, Debug)]
-#[error(display = "Dummy offline check error")]
-pub struct Error;
-
-pub struct MonitorHandle;
-
-impl MonitorHandle {
- pub fn is_offline(&self) -> bool {
- false
- }
-}
-
-pub fn spawn_monitor(
- _sender: Weak<UnboundedSender<TunnelCommand>>,
-) -> Result<MonitorHandle, Error> {
- Ok(MonitorHandle)
-}
diff --git a/talpid-core/src/offline/mod.rs b/talpid-core/src/offline/mod.rs
index 2092da2f0a..268193407e 100644
--- a/talpid-core/src/offline/mod.rs
+++ b/talpid-core/src/offline/mod.rs
@@ -1,6 +1,8 @@
use crate::tunnel_state_machine::TunnelCommand;
use futures::sync::mpsc::UnboundedSender;
use std::sync::Weak;
+#[cfg(target_os = "android")]
+use talpid_types::android::AndroidContext;
#[cfg(target_os = "macos")]
#[path = "macos.rs"]
@@ -14,8 +16,8 @@ mod imp;
#[path = "linux.rs"]
mod imp;
-#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
-#[path = "dummy.rs"]
+#[cfg(target_os = "android")]
+#[path = "android.rs"]
mod imp;
pub use self::imp::Error;
@@ -28,6 +30,13 @@ impl MonitorHandle {
}
}
-pub fn spawn_monitor(sender: Weak<UnboundedSender<TunnelCommand>>) -> Result<MonitorHandle, Error> {
- Ok(MonitorHandle(imp::spawn_monitor(sender)?))
+pub fn spawn_monitor(
+ sender: Weak<UnboundedSender<TunnelCommand>>,
+ #[cfg(target_os = "android")] android_context: AndroidContext,
+) -> Result<MonitorHandle, Error> {
+ Ok(MonitorHandle(imp::spawn_monitor(
+ sender,
+ #[cfg(target_os = "android")]
+ android_context,
+ )?))
}
diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs
index d81a787f1a..b86795491d 100644
--- a/talpid-core/src/tunnel_state_machine/mod.rs
+++ b/talpid-core/src/tunnel_state_machine/mod.rs
@@ -78,8 +78,12 @@ where
{
let (command_tx, command_rx) = mpsc::unbounded();
let command_tx = Arc::new(command_tx);
- let offline_monitor =
- offline::spawn_monitor(Arc::downgrade(&command_tx)).map_err(Error::OfflineMonitorError)?;
+ let offline_monitor = offline::spawn_monitor(
+ Arc::downgrade(&command_tx),
+ #[cfg(target_os = "android")]
+ android_context.clone(),
+ )
+ .map_err(Error::OfflineMonitorError)?;
let is_offline = offline_monitor.is_offline();
let tun_provider = TunProvider::new(
diff --git a/talpid-types/src/android/mod.rs b/talpid-types/src/android/mod.rs
index 70c34fea95..1c8522509d 100644
--- a/talpid-types/src/android/mod.rs
+++ b/talpid-types/src/android/mod.rs
@@ -4,3 +4,16 @@ pub struct AndroidContext {
pub jvm: JavaVM,
pub vpn_service: GlobalRef,
}
+
+impl Clone for AndroidContext {
+ fn clone(&self) -> Self {
+ let jvm_pointer = self.jvm.get_java_vm_pointer();
+ let jvm =
+ unsafe { JavaVM::from_raw(jvm_pointer).expect("Failed to get pointer to Java VM") };
+
+ AndroidContext {
+ jvm,
+ vpn_service: self.vpn_service.clone(),
+ }
+ }
+}