summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-11-19 10:25:44 +0100
committerDavid Lönnhager <david.l@mullvad.net>2024-11-22 13:38:16 +0100
commitf4db85b3a552f60d2454bfa69912c7ced51b41b1 (patch)
treec932bc8d75ea3ca6d95dfdd0c3925a171cea9d07 /talpid-core/src
parent8ababf0f77b23f7245a1aed3d8c8c4a5e3c06192 (diff)
downloadmullvadvpn-f4db85b3a552f60d2454bfa69912c7ced51b41b1.tar.xz
mullvadvpn-f4db85b3a552f60d2454bfa69912c7ced51b41b1.zip
Add non-blocking DNS resolver for Android API requests
Diffstat (limited to 'talpid-core/src')
-rw-r--r--talpid-core/src/connectivity_listener.rs249
-rw-r--r--talpid-core/src/lib.rs4
-rw-r--r--talpid-core/src/offline/android.rs211
-rw-r--r--talpid-core/src/offline/mod.rs8
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs13
5 files changed, 279 insertions, 206 deletions
diff --git a/talpid-core/src/connectivity_listener.rs b/talpid-core/src/connectivity_listener.rs
new file mode 100644
index 0000000000..b28f58cefb
--- /dev/null
+++ b/talpid-core/src/connectivity_listener.rs
@@ -0,0 +1,249 @@
+//! Rust wrapper around Android connectivity listener
+
+use futures::channel::mpsc::UnboundedSender;
+use jnix::{
+ jni::{
+ self,
+ objects::{GlobalRef, JObject, JValue},
+ signature::{JavaType, Primitive},
+ sys::{jboolean, jlong, JNI_TRUE},
+ JNIEnv, JavaVM,
+ },
+ JnixEnv,
+ FromJava,
+};
+use std::{net::IpAddr, sync::{Arc, Weak}};
+use talpid_types::{android::AndroidContext, net::Connectivity, ErrorExt};
+
+/// Error related to Android connectivity monitor
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ /// Failed to attach Java VM to tunnel thread
+ #[error("Failed to attach Java VM to tunnel thread")]
+ AttachJvmToThread(#[source] jni::errors::Error),
+
+ /// Failed to call Java method
+ #[error("Failed to call Java method {0}.{1}")]
+ CallMethod(&'static str, &'static str, #[source] jni::errors::Error),
+
+ /// Failed to create global reference to Java object
+ #[error("Failed to create global reference to Java object")]
+ CreateGlobalRef(#[source] jni::errors::Error),
+
+ /// Failed to find method
+ #[error("Failed to find {0}.{1} method")]
+ FindMethod(&'static str, &'static str, #[source] jni::errors::Error),
+
+ /// Method returned invalid result
+ #[error("Received an invalid result from {0}.{1}: {2}")]
+ InvalidMethodResult(&'static str, &'static str, String),
+}
+
+/// Android connectivity listener
+#[derive(Clone)]
+pub struct ConnectivityListener {
+ jvm: Arc<JavaVM>,
+ class: GlobalRef,
+ object: GlobalRef,
+ _sender: Option<Arc<UnboundedSender<Connectivity>>>,
+}
+
+impl ConnectivityListener {
+ /// Create a new connectivity listener
+ 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(ConnectivityListener {
+ jvm: android_context.jvm,
+ class,
+ object,
+ _sender: None,
+ })
+ }
+
+ /// Register a channel that receives changes about the offline state
+ pub fn set_connectivity_listener(
+ &mut self,
+ sender: UnboundedSender<Connectivity>,
+ ) -> Result<(), Error> {
+ let sender = Arc::new(sender);
+
+ let weak_sender = Arc::downgrade(&sender);
+
+ let weak_sender_ptr = Box::new(weak_sender);
+ let weak_sender_address = Box::into_raw(weak_sender_ptr) as jlong;
+
+ let result = self.call_method(
+ "setSenderAddress",
+ "(J)V",
+ &[JValue::Long(weak_sender_address)],
+ JavaType::Primitive(Primitive::Void),
+ )?;
+
+ match result {
+ JValue::Void => Ok(()),
+ value => Err(Error::InvalidMethodResult(
+ "ConnectivityListener",
+ "setSenderAddress",
+ format!("{:?}", value),
+ )),
+ }?;
+
+ self._sender = Some(sender);
+
+ Ok(())
+ }
+
+ /// Return the current offline/connectivity state
+ pub fn connectivity(&self) -> Connectivity {
+ self.get_is_connected()
+ .map(|connected| Connectivity::Status { connected })
+ .unwrap_or_else(|error| {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to check connectivity status")
+ );
+ Connectivity::PresumeOnline
+ })
+ }
+
+ fn get_is_connected(&self) -> Result<bool, Error> {
+ let is_connected = self.call_method(
+ "isConnected",
+ "()Z",
+ &[],
+ JavaType::Primitive(Primitive::Boolean),
+ )?;
+
+ match is_connected {
+ JValue::Bool(JNI_TRUE) => Ok(true),
+ JValue::Bool(_) => Ok(false),
+ value => Err(Error::InvalidMethodResult(
+ "ConnectivityListener",
+ "isConnected",
+ format!("{:?}", value),
+ )),
+ }
+ }
+
+ /// Return the current DNS servers according to Android
+ pub fn current_dns_servers(&self) -> Result<Vec<IpAddr>, Error> {
+ let env = JnixEnv::from(
+ self.jvm
+ .attach_current_thread_as_daemon()
+ .map_err(Error::AttachJvmToThread)?,
+ );
+
+ let current_dns_servers = self.call_method(
+ "getCurrentDnsServers",
+ "()Ljava/util/ArrayList;",
+ &[],
+ JavaType::Object("java/util/ArrayList".to_owned()),
+ )?;
+
+ match current_dns_servers {
+ JValue::Object(jaddrs) => Ok(Vec::from_java(&env, jaddrs)),
+ value => Err(Error::InvalidMethodResult(
+ "ConnectivityListener",
+ "currentDnsServers",
+ 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<'_>,
+ connected: jboolean,
+ sender_address: jlong,
+) {
+ let connected = JNI_TRUE == connected;
+ let sender_ref = Box::leak(unsafe { get_sender_from_address(sender_address) });
+ if let Some(sender) = sender_ref.upgrade() {
+ if sender
+ .unbounded_send(Connectivity::Status { connected })
+ .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<Connectivity>>> {
+ Box::from_raw(address as *mut Weak<UnboundedSender<Connectivity>>)
+}
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs
index bb81d7a48f..585c0274ee 100644
--- a/talpid-core/src/lib.rs
+++ b/talpid-core/src/lib.rs
@@ -42,3 +42,7 @@ mod linux;
/// A resolver that's controlled by the tunnel state machine
#[cfg(target_os = "macos")]
pub(crate) mod resolver;
+
+/// Connectivity monitor for Android
+#[cfg(target_os = "android")]
+pub mod connectivity_listener;
diff --git a/talpid-core/src/offline/android.rs b/talpid-core/src/offline/android.rs
index 7dc8389ed3..7280ee792f 100644
--- a/talpid-core/src/offline/android.rs
+++ b/talpid-core/src/offline/android.rs
@@ -1,217 +1,32 @@
+use crate::connectivity_listener::{ConnectivityListener, Error};
use futures::channel::mpsc::UnboundedSender;
-use jnix::{
- jni::{
- self,
- objects::{GlobalRef, JObject, JValue},
- signature::{JavaType, Primitive},
- sys::{jboolean, jlong, JNI_TRUE},
- JNIEnv, JavaVM,
- },
- JnixEnv,
-};
-use std::sync::{Arc, Weak};
-use talpid_types::{android::AndroidContext, net::Connectivity, ErrorExt};
-
-#[derive(thiserror::Error, Debug)]
-pub enum Error {
- #[error("Failed to attach Java VM to tunnel thread")]
- AttachJvmToThread(#[source] jni::errors::Error),
-
- #[error("Failed to call Java method {0}.{1}")]
- CallMethod(&'static str, &'static str, #[source] jni::errors::Error),
-
- #[error("Failed to create global reference to Java object")]
- CreateGlobalRef(#[source] jni::errors::Error),
-
- #[error("Failed to find {0}.{1} method")]
- FindMethod(&'static str, &'static str, #[source] jni::errors::Error),
-
- #[error("Received an invalid result from {0}.{1}: {2}")]
- InvalidMethodResult(&'static str, &'static str, String),
-}
+use talpid_types::net::Connectivity;
pub struct MonitorHandle {
- jvm: Arc<JavaVM>,
- class: GlobalRef,
- object: GlobalRef,
- _sender: Arc<UnboundedSender<Connectivity>>,
+ connectivity_listener: ConnectivityListener,
}
impl MonitorHandle {
- pub fn new(
- android_context: AndroidContext,
- sender: Arc<UnboundedSender<Connectivity>>,
- ) -> 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,
- _sender: sender,
- })
+ fn new(connectivity_listener: ConnectivityListener) -> Self {
+ MonitorHandle {
+ connectivity_listener,
+ }
}
#[allow(clippy::unused_async)]
pub async fn connectivity(&self) -> Connectivity {
- self.get_is_connected()
- .map(|connected| Connectivity::Status { connected })
- .unwrap_or_else(|error| {
- log::error!(
- "{}",
- error.display_chain_with_msg("Failed to check connectivity status")
- );
- Connectivity::PresumeOnline
- })
- }
-
- fn get_is_connected(&self) -> Result<bool, Error> {
- let is_connected = self.call_method(
- "isConnected",
- "()Z",
- &[],
- JavaType::Primitive(Primitive::Boolean),
- )?;
-
- match is_connected {
- JValue::Bool(JNI_TRUE) => Ok(true),
- JValue::Bool(_) => Ok(false),
- value => Err(Error::InvalidMethodResult(
- "ConnectivityListener",
- "isConnected",
- format!("{:?}", value),
- )),
- }
- }
-
- fn set_sender(&self, sender: Weak<UnboundedSender<Connectivity>>) -> 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))
+ self.connectivity_listener.connectivity()
}
}
-/// 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<'_>,
- connected: jboolean,
- sender_address: jlong,
-) {
- let connected = JNI_TRUE == connected;
- let sender_ref = Box::leak(unsafe { get_sender_from_address(sender_address) });
- if let Some(sender) = sender_ref.upgrade() {
- if sender
- .unbounded_send(Connectivity::Status { connected })
- .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<Connectivity>>> {
- Box::from_raw(address as *mut Weak<UnboundedSender<Connectivity>>)
-}
-
#[allow(clippy::unused_async)]
pub async fn spawn_monitor(
sender: UnboundedSender<Connectivity>,
- android_context: AndroidContext,
+ connectivity_listener: ConnectivityListener,
) -> Result<MonitorHandle, Error> {
- let sender = Arc::new(sender);
- let weak_sender = Arc::downgrade(&sender);
- let monitor_handle = MonitorHandle::new(android_context, sender)?;
-
- monitor_handle.set_sender(weak_sender)?;
-
+ let mut monitor_handle = MonitorHandle::new(connectivity_listener);
+ monitor_handle
+ .connectivity_listener
+ .set_connectivity_listener(sender)?;
Ok(monitor_handle)
}
diff --git a/talpid-core/src/offline/mod.rs b/talpid-core/src/offline/mod.rs
index 0e1d55c273..6605bd3358 100644
--- a/talpid-core/src/offline/mod.rs
+++ b/talpid-core/src/offline/mod.rs
@@ -1,9 +1,9 @@
+#[cfg(target_os = "android")]
+use crate::connectivity_listener::ConnectivityListener;
use futures::channel::mpsc::UnboundedSender;
use std::sync::LazyLock;
#[cfg(not(target_os = "android"))]
use talpid_routing::RouteManagerHandle;
-#[cfg(target_os = "android")]
-use talpid_types::android::AndroidContext;
use talpid_types::{net::Connectivity, ErrorExt};
#[cfg(target_os = "macos")]
@@ -44,7 +44,7 @@ pub async fn spawn_monitor(
sender: UnboundedSender<Connectivity>,
#[cfg(not(target_os = "android"))] route_manager: RouteManagerHandle,
#[cfg(target_os = "linux")] fwmark: Option<u32>,
- #[cfg(target_os = "android")] android_context: AndroidContext,
+ #[cfg(target_os = "android")] connectivity_listener: ConnectivityListener,
) -> MonitorHandle {
let monitor = if *FORCE_DISABLE_OFFLINE_MONITOR {
None
@@ -56,7 +56,7 @@ pub async fn spawn_monitor(
#[cfg(target_os = "linux")]
fwmark,
#[cfg(target_os = "android")]
- android_context,
+ connectivity_listener,
)
.await
.inspect_err(|error| {
diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs
index 6a1d779be8..2541bc88e6 100644
--- a/talpid-core/src/tunnel_state_machine/mod.rs
+++ b/talpid-core/src/tunnel_state_machine/mod.rs
@@ -49,6 +49,9 @@ use talpid_types::{
tunnel::{ErrorStateCause, ParameterGenerationError, TunnelStateTransition},
};
+#[cfg(target_os = "android")]
+use crate::connectivity_listener::ConnectivityListener;
+
const TUNNEL_STATE_MACHINE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
/// Errors that can happen when setting up or using the state machine.
@@ -119,6 +122,7 @@ pub struct LinuxNetworkingIdentifiers {
}
/// Spawn the tunnel state machine thread, returning a channel for sending tunnel commands.
+#[allow(clippy::too_many_arguments)]
pub async fn spawn(
initial_settings: InitialTunnelState,
tunnel_parameters_generator: impl TunnelParametersGenerator,
@@ -128,6 +132,7 @@ pub async fn spawn(
offline_state_listener: mpsc::UnboundedSender<Connectivity>,
#[cfg(target_os = "windows")] volume_update_rx: mpsc::UnboundedReceiver<()>,
#[cfg(target_os = "android")] android_context: AndroidContext,
+ #[cfg(target_os = "android")] connectivity_listener: ConnectivityListener,
#[cfg(target_os = "linux")] linux_ids: LinuxNetworkingIdentifiers,
) -> Result<TunnelStateMachineHandle, Error> {
let (command_tx, command_rx) = mpsc::unbounded();
@@ -155,7 +160,7 @@ pub async fn spawn(
#[cfg(target_os = "windows")]
volume_update_rx,
#[cfg(target_os = "android")]
- android_context,
+ connectivity_listener,
#[cfg(target_os = "linux")]
linux_ids,
};
@@ -251,7 +256,7 @@ struct TunnelStateMachineInitArgs<G: TunnelParametersGenerator> {
#[cfg(target_os = "windows")]
volume_update_rx: mpsc::UnboundedReceiver<()>,
#[cfg(target_os = "android")]
- android_context: AndroidContext,
+ connectivity_listener: ConnectivityListener,
#[cfg(target_os = "linux")]
linux_ids: LinuxNetworkingIdentifiers,
}
@@ -263,7 +268,7 @@ impl TunnelStateMachine {
#[cfg(target_os = "windows")]
let volume_update_rx = args.volume_update_rx;
#[cfg(target_os = "android")]
- let android_context = args.android_context;
+ let connectivity_listener = args.connectivity_listener;
let runtime = tokio::runtime::Handle::current();
@@ -339,7 +344,7 @@ impl TunnelStateMachine {
#[cfg(target_os = "linux")]
Some(args.linux_ids.fwmark),
#[cfg(target_os = "android")]
- android_context,
+ connectivity_listener,
)
.await;
let connectivity = offline_monitor.connectivity().await;