summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-12-03 15:19:21 +0000
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-12-04 12:17:24 +0000
commitdf68bd41a8564905372cd82a4bb43760926dd261 (patch)
tree651d58b75191dcfb421106ee4db8dee407a53c2d
parent50a93e08baf26a09c6c09a2a29ba390e430a70d3 (diff)
downloadmullvadvpn-df68bd41a8564905372cd82a4bb43760926dd261.tar.xz
mullvadvpn-df68bd41a8564905372cd82a4bb43760926dd261.zip
Implement offline monitor for Android
-rw-r--r--mullvad-jni/src/classes.rs1
-rw-r--r--talpid-core/src/offline/android.rs179
2 files changed, 168 insertions, 12 deletions
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
index dbef66832f..eb33331314 100644
--- a/talpid-core/src/offline/android.rs
+++ b/talpid-core/src/offline/android.rs
@@ -1,22 +1,173 @@
use crate::tunnel_state_machine::TunnelCommand;
use futures::sync::mpsc::UnboundedSender;
-use jnix::jni::{
- objects::JObject,
- sys::{jboolean, jlong, JNI_FALSE},
- JNIEnv,
+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;
+use talpid_types::{android::AndroidContext, ErrorExt};
#[derive(err_derive::Error, Debug)]
-#[error(display = "Unknown offline monitor error")]
-pub struct Error;
+#[error(no_from)]
+pub enum Error {
+ #[error(display = "Failed to attach Java VM to tunnel thread")]
+ AttachJvmToThread(#[error(source)] jni::errors::Error),
-pub struct MonitorHandle;
+ #[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 {
- false
+ 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))
}
}
@@ -55,8 +206,12 @@ unsafe fn get_sender_from_address(address: jlong) -> Box<Weak<UnboundedSender<Tu
}
pub fn spawn_monitor(
- _sender: Weak<UnboundedSender<TunnelCommand>>,
- _android_context: AndroidContext,
+ sender: Weak<UnboundedSender<TunnelCommand>>,
+ android_context: AndroidContext,
) -> Result<MonitorHandle, Error> {
- Ok(MonitorHandle)
+ let monitor_handle = MonitorHandle::new(android_context)?;
+
+ monitor_handle.set_sender(sender)?;
+
+ Ok(monitor_handle)
}