diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-04-11 13:37:57 +0000 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-05-23 18:08:56 +0000 |
| commit | ee614ce4f1cb61961ca6e65a87902897d9315ead (patch) | |
| tree | 0f8d0ac57e20a2d9f3d36df9f137f595e5dde42c | |
| parent | 01206472d7af02de6308ec1bf6315e1b0e5c9f39 (diff) | |
| download | mullvadvpn-ee614ce4f1cb61961ca6e65a87902897d9315ead.tar.xz mullvadvpn-ee614ce4f1cb61961ca6e65a87902897d9315ead.zip | |
Implement listening for tunnel state change events
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt | 7 | ||||
| -rw-r--r-- | mullvad-jni/src/jni_event_listener.rs | 123 | ||||
| -rw-r--r-- | mullvad-jni/src/lib.rs | 48 |
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(), |
