diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-12-04 09:38:41 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-12-04 09:38:41 -0300 |
| commit | 6e9d2c96c9fbd13f06d3d1ab2f4b36a55995d3ae (patch) | |
| tree | eb2779084909cdc1a07b70f5178ad7fd741aa0ff | |
| parent | 09cd80e4b818ab66876b059c94ecc169feccd0dc (diff) | |
| parent | 9c5a7afff17913d8adeff3ea3fa2c972bfa9b00b (diff) | |
| download | mullvadvpn-6e9d2c96c9fbd13f06d3d1ab2f4b36a55995d3ae.tar.xz mullvadvpn-6e9d2c96c9fbd13f06d3d1ab2f4b36a55995d3ae.zip | |
Merge branch 'android-offline-check'
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt | 2 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt | 68 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt | 10 | ||||
| -rw-r--r-- | mullvad-jni/src/classes.rs | 1 | ||||
| -rw-r--r-- | talpid-core/src/offline/android.rs | 217 | ||||
| -rw-r--r-- | talpid-core/src/offline/dummy.rs | 21 | ||||
| -rw-r--r-- | talpid-core/src/offline/mod.rs | 17 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 8 | ||||
| -rw-r--r-- | talpid-types/src/android/mod.rs | 13 |
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(), + } + } +} |
