summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-04-11 13:37:57 +0000
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-05-23 18:08:56 +0000
commitee614ce4f1cb61961ca6e65a87902897d9315ead (patch)
tree0f8d0ac57e20a2d9f3d36df9f137f595e5dde42c
parent01206472d7af02de6308ec1bf6315e1b0e5c9f39 (diff)
downloadmullvadvpn-ee614ce4f1cb61961ca6e65a87902897d9315ead.tar.xz
mullvadvpn-ee614ce4f1cb61961ca6e65a87902897d9315ead.zip
Implement listening for tunnel state change events
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt7
-rw-r--r--mullvad-jni/src/jni_event_listener.rs123
-rw-r--r--mullvad-jni/src/lib.rs48
3 files changed, 157 insertions, 21 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
index e95a761ba5..80ac09eef2 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
@@ -4,6 +4,7 @@ import net.mullvad.mullvadvpn.model.AccountData
import net.mullvad.mullvadvpn.model.RelayList
import net.mullvad.mullvadvpn.model.RelaySettingsUpdate
import net.mullvad.mullvadvpn.model.Settings
+import net.mullvad.mullvadvpn.model.TunnelStateTransition
class MullvadDaemon {
init {
@@ -11,6 +12,8 @@ class MullvadDaemon {
initialize()
}
+ var onTunnelStateChange: ((TunnelStateTransition) -> Unit)? = null
+
external fun connect()
external fun disconnect()
external fun getAccountData(accountToken: String): AccountData?
@@ -20,4 +23,8 @@ class MullvadDaemon {
external fun updateRelaySettings(update: RelaySettingsUpdate)
private external fun initialize()
+
+ private fun notifyTunnelStateEvent(event: TunnelStateTransition) {
+ onTunnelStateChange?.invoke(event)
+ }
}
diff --git a/mullvad-jni/src/jni_event_listener.rs b/mullvad-jni/src/jni_event_listener.rs
new file mode 100644
index 0000000000..7beb7ae844
--- /dev/null
+++ b/mullvad-jni/src/jni_event_listener.rs
@@ -0,0 +1,123 @@
+use crate::{get_class, into_java::IntoJava};
+use jni::{
+ objects::{JMethodID, JObject, JValue},
+ signature::{JavaType, Primitive},
+ AttachGuard, JNIEnv,
+};
+use mullvad_daemon::EventListener;
+use mullvad_types::{relay_list::RelayList, settings::Settings};
+use std::{sync::mpsc, thread};
+use talpid_types::{tunnel::TunnelStateTransition, ErrorExt};
+
+#[derive(Debug, err_derive::Error)]
+pub enum Error {
+ #[error(display = "Failed to create global reference to MullvadDaemon Java object")]
+ CreateGlobalReference(#[error(cause)] jni::errors::Error),
+
+ #[error(display = "Failed to find {} method", _0)]
+ FindMethod(&'static str, #[error(cause)] jni::errors::Error),
+
+ #[error(display = "Failed to retrieve Java VM instance")]
+ GetJvmInstance(#[error(cause)] jni::errors::Error),
+}
+
+#[derive(Clone, Debug)]
+pub struct JniEventListener(mpsc::Sender<TunnelStateTransition>);
+
+impl JniEventListener {
+ pub fn spawn(env: &JNIEnv, mullvad_daemon: &JObject) -> Result<Self, Error> {
+ JniEventHandler::spawn(env, mullvad_daemon)
+ }
+}
+
+impl EventListener for JniEventListener {
+ fn notify_new_state(&self, state: TunnelStateTransition) {
+ let _ = self.0.send(state);
+ }
+
+ fn notify_settings(&self, _: Settings) {}
+ fn notify_relay_list(&self, _: RelayList) {}
+}
+
+struct JniEventHandler<'env> {
+ env: AttachGuard<'env>,
+ mullvad_ipc_client: JObject<'env>,
+ notify_tunnel_event: JMethodID<'env>,
+ events: mpsc::Receiver<TunnelStateTransition>,
+}
+
+impl JniEventHandler<'_> {
+ pub fn spawn(
+ old_env: &JNIEnv,
+ old_mullvad_ipc_client: &JObject,
+ ) -> Result<JniEventListener, Error> {
+ let (tx, rx) = mpsc::channel();
+ let jvm = old_env.get_java_vm().map_err(Error::GetJvmInstance)?;
+ let mullvad_ipc_client = old_env
+ .new_global_ref(*old_mullvad_ipc_client)
+ .map_err(Error::CreateGlobalReference)?;
+
+ thread::spawn(move || match jvm.attach_current_thread() {
+ Ok(env) => match JniEventHandler::new(env, mullvad_ipc_client.as_obj(), rx) {
+ Ok(mut listener) => listener.run(),
+ Err(error) => log::error!("{}", error.display_chain()),
+ },
+ Err(error) => {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg(
+ "Failed to attach tunnel event listener thread to Java VM"
+ )
+ );
+ }
+ });
+
+ Ok(JniEventListener(tx))
+ }
+}
+
+impl<'env> JniEventHandler<'env> {
+ fn new(
+ env: AttachGuard<'env>,
+ mullvad_ipc_client: JObject<'env>,
+ events: mpsc::Receiver<TunnelStateTransition>,
+ ) -> Result<Self, Error> {
+ let class = get_class("net/mullvad/mullvadvpn/MullvadDaemon");
+ let notify_tunnel_event = env
+ .get_method_id(
+ &class,
+ "notifyTunnelStateEvent",
+ "(Lnet/mullvad/mullvadvpn/model/TunnelStateTransition;)V",
+ )
+ .map_err(|error| Error::FindMethod("notifyTunnelStateEvent", error))?;
+
+ Ok(JniEventHandler {
+ env,
+ mullvad_ipc_client,
+ notify_tunnel_event,
+ events,
+ })
+ }
+
+ fn run(&mut self) {
+ while let Ok(event) = self.events.recv() {
+ self.handle_tunnel_event(event);
+ }
+ }
+
+ fn handle_tunnel_event(&self, event: TunnelStateTransition) {
+ let result = self.env.call_method_unchecked(
+ self.mullvad_ipc_client,
+ self.notify_tunnel_event,
+ JavaType::Primitive(Primitive::Void),
+ &[JValue::Object(event.into_java(&self.env))],
+ );
+
+ if let Err(error) = result {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to call MullvadDaemon.notifyTunnelStateEvent")
+ );
+ }
+ }
+}
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index 45351e2563..83e60e0486 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -4,18 +4,21 @@ mod daemon_interface;
mod from_java;
mod into_java;
mod is_null;
+mod jni_event_listener;
-use crate::{daemon_interface::DaemonInterface, from_java::FromJava, into_java::IntoJava};
+use crate::{
+ daemon_interface::DaemonInterface, from_java::FromJava, into_java::IntoJava,
+ jni_event_listener::JniEventListener,
+};
use jni::{
objects::{GlobalRef, JObject, JString},
JNIEnv,
};
use lazy_static::lazy_static;
-use mullvad_daemon::{logging, version, Daemon, DaemonCommandSender, EventListener};
-use mullvad_types::{relay_list::RelayList, settings::Settings};
+use mullvad_daemon::{logging, version, Daemon, DaemonCommandSender};
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, path::PathBuf, sync::mpsc, thread};
-use talpid_types::{tunnel::TunnelStateTransition, ErrorExt};
+use talpid_types::ErrorExt;
const LOG_FILENAME: &str = "daemon.log";
@@ -41,6 +44,7 @@ const CLASSES_TO_LOAD: &[&str] = &[
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Connecting",
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Disconnected",
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Disconnecting",
+ "net/mullvad/mullvadvpn/MullvadDaemon",
];
lazy_static! {
@@ -56,19 +60,22 @@ pub enum Error {
#[error(display = "Failed to initialize the mullvad daemon")]
InitializeDaemon(#[error(cause)] mullvad_daemon::Error),
+
+ #[error(display = "Failed to spawn the JNI event listener")]
+ SpawnJniEventListener(#[error(cause)] jni_event_listener::Error),
}
#[no_mangle]
#[allow(non_snake_case)]
pub extern "system" fn Java_net_mullvad_mullvadvpn_MullvadDaemon_initialize(
env: JNIEnv,
- _: JObject,
+ this: JObject,
) {
let log_dir = start_logging();
load_classes(&env);
- if let Err(error) = initialize(log_dir) {
+ if let Err(error) = initialize(&env, &this, log_dir) {
log::error!("{}", error.display_chain());
}
}
@@ -102,8 +109,8 @@ fn load_class_reference(env: &JNIEnv, name: &str) -> GlobalRef {
.expect("Failed to convert local reference to Java class into a global reference")
}
-fn initialize(log_dir: PathBuf) -> Result<(), Error> {
- let daemon_command_sender = spawn_daemon(log_dir)?;
+fn initialize(env: &JNIEnv, this: &JObject, log_dir: PathBuf) -> Result<(), Error> {
+ let daemon_command_sender = spawn_daemon(env, this, log_dir)?;
DAEMON_INTERFACE
.lock()
@@ -112,10 +119,15 @@ fn initialize(log_dir: PathBuf) -> Result<(), Error> {
Ok(())
}
-fn spawn_daemon(log_dir: PathBuf) -> Result<DaemonCommandSender, Error> {
+fn spawn_daemon(
+ env: &JNIEnv,
+ this: &JObject,
+ log_dir: PathBuf,
+) -> Result<DaemonCommandSender, Error> {
+ let listener = JniEventListener::spawn(env, this).map_err(Error::SpawnJniEventListener)?;
let (tx, rx) = mpsc::channel();
- thread::spawn(move || match create_daemon(log_dir) {
+ thread::spawn(move || match create_daemon(listener, log_dir) {
Ok(daemon) => {
let _ = tx.send(Ok(daemon.command_sender()));
match daemon.run() {
@@ -131,12 +143,15 @@ fn spawn_daemon(log_dir: PathBuf) -> Result<DaemonCommandSender, Error> {
rx.recv().unwrap()
}
-fn create_daemon(log_dir: PathBuf) -> Result<Daemon<DummyListener>, Error> {
+fn create_daemon(
+ listener: JniEventListener,
+ log_dir: PathBuf,
+) -> Result<Daemon<JniEventListener>, Error> {
let resource_dir = mullvad_paths::get_resource_dir();
let cache_dir = mullvad_paths::cache_dir().map_err(Error::GetCacheDir)?;
let daemon = Daemon::start_with_event_listener(
- DummyListener,
+ listener,
Some(log_dir),
resource_dir,
cache_dir,
@@ -147,15 +162,6 @@ fn create_daemon(log_dir: PathBuf) -> Result<Daemon<DummyListener>, Error> {
Ok(daemon)
}
-#[derive(Clone, Copy, Debug)]
-struct DummyListener;
-
-impl EventListener for DummyListener {
- fn notify_new_state(&self, _: TunnelStateTransition) {}
- fn notify_settings(&self, _: Settings) {}
- fn notify_relay_list(&self, _: RelayList) {}
-}
-
fn get_class(name: &str) -> GlobalRef {
match CLASSES.read().get(name) {
Some(class) => class.clone(),