summaryrefslogtreecommitdiffhomepage
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
parent8ababf0f77b23f7245a1aed3d8c8c4a5e3c06192 (diff)
downloadmullvadvpn-f4db85b3a552f60d2454bfa69912c7ced51b41b1.tar.xz
mullvadvpn-f4db85b3a552f60d2454bfa69912c7ced51b41b1.zip
Add non-blocking DNS resolver for Android API requests
-rw-r--r--Cargo.lock3
-rw-r--r--Cargo.toml3
-rw-r--r--mullvad-api/Cargo.toml1
-rw-r--r--mullvad-api/src/bin/relay_list.rs6
-rw-r--r--mullvad-api/src/https_client_with_sni.rs36
-rw-r--r--mullvad-api/src/lib.rs52
-rw-r--r--mullvad-api/src/rest.rs3
-rw-r--r--mullvad-daemon/Cargo.toml2
-rw-r--r--mullvad-daemon/src/android_dns.rs49
-rw-r--r--mullvad-daemon/src/lib.rs23
-rw-r--r--mullvad-encrypted-dns-proxy/Cargo.toml2
-rw-r--r--mullvad-problem-report/src/lib.rs3
-rw-r--r--mullvad-setup/src/main.rs4
-rw-r--r--talpid-core/Cargo.toml4
-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
-rw-r--r--test/Cargo.lock62
-rw-r--r--test/test-manager/src/tests/account.rs7
21 files changed, 480 insertions, 265 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d173471673..6d660b4723 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2318,6 +2318,7 @@ dependencies = [
name = "mullvad-api"
version = "0.0.0"
dependencies = [
+ "async-trait",
"cbindgen",
"chrono",
"futures",
@@ -2373,6 +2374,7 @@ name = "mullvad-daemon"
version = "0.0.0"
dependencies = [
"android_logger",
+ "async-trait",
"chrono",
"clap",
"ctrlc",
@@ -2380,6 +2382,7 @@ dependencies = [
"either",
"fern",
"futures",
+ "hickory-resolver",
"libc",
"log",
"log-panics",
diff --git a/Cargo.toml b/Cargo.toml
index da59a6a6c8..8c2d22a043 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -67,6 +67,9 @@ single_use_lifetimes = "warn"
unused_async = "deny"
[workspace.dependencies]
+hickory-proto = "0.24.1"
+hickory-resolver = "0.24.1"
+hickory-server = { version = "0.24.1", features = ["resolver"] }
tokio = { version = "1.8" }
parity-tokio-ipc = "0.9"
futures = "0.3.15"
diff --git a/mullvad-api/Cargo.toml b/mullvad-api/Cargo.toml
index e617d942b5..a822593600 100644
--- a/mullvad-api/Cargo.toml
+++ b/mullvad-api/Cargo.toml
@@ -15,6 +15,7 @@ workspace = true
api-override = []
[dependencies]
+async-trait = "0.1"
libc = "0.2"
chrono = { workspace = true }
thiserror = { workspace = true }
diff --git a/mullvad-api/src/bin/relay_list.rs b/mullvad-api/src/bin/relay_list.rs
index def32303ea..22190abd63 100644
--- a/mullvad-api/src/bin/relay_list.rs
+++ b/mullvad-api/src/bin/relay_list.rs
@@ -2,13 +2,15 @@
//! Used by the installer artifact packer to bundle the latest available
//! relay list at the time of creating the installer.
-use mullvad_api::{proxy::ApiConnectionMode, rest::Error as RestError, RelayListProxy};
+use mullvad_api::{
+ proxy::ApiConnectionMode, rest::Error as RestError, DefaultDnsResolver, RelayListProxy,
+};
use std::process;
use talpid_types::ErrorExt;
#[tokio::main]
async fn main() {
- let runtime = mullvad_api::Runtime::new(tokio::runtime::Handle::current())
+ let runtime = mullvad_api::Runtime::new(tokio::runtime::Handle::current(), DefaultDnsResolver)
.expect("Failed to load runtime");
let relay_list_request =
diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs
index 898927513f..09e198ca3b 100644
--- a/mullvad-api/src/https_client_with_sni.rs
+++ b/mullvad-api/src/https_client_with_sni.rs
@@ -2,17 +2,14 @@ use crate::{
abortable_stream::{AbortableStream, AbortableStreamHandle},
proxy::{ApiConnection, ApiConnectionMode, ProxyConfig},
tls_stream::TlsStream,
- AddressCache,
+ AddressCache, DnsResolver,
};
use futures::{channel::mpsc, future, pin_mut, StreamExt};
#[cfg(target_os = "android")]
use futures::{channel::oneshot, sink::SinkExt};
use http::uri::Scheme;
use hyper::Uri;
-use hyper_util::{
- client::legacy::connect::dns::{GaiResolver, Name},
- rt::TokioIo,
-};
+use hyper_util::rt::TokioIo;
use mullvad_encrypted_dns_proxy::{
config::ProxyConfig as EncryptedDNSConfig, Forwarder as EncryptedDNSForwarder,
};
@@ -291,6 +288,7 @@ pub struct HttpsConnectorWithSni {
sni_hostname: Option<String>,
address_cache: AddressCache,
abort_notify: Arc<tokio::sync::Notify>,
+ dns_resolver: Arc<dyn DnsResolver>,
#[cfg(target_os = "android")]
socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>,
}
@@ -307,6 +305,7 @@ impl HttpsConnectorWithSni {
pub fn new(
sni_hostname: Option<String>,
address_cache: AddressCache,
+ dns_resolver: Arc<dyn DnsResolver>,
#[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>,
) -> (Self, HttpsConnectorWithSniHandle) {
let (tx, mut rx) = mpsc::unbounded();
@@ -355,6 +354,7 @@ impl HttpsConnectorWithSni {
sni_hostname,
address_cache,
abort_notify,
+ dns_resolver,
#[cfg(target_os = "android")]
socket_bypass_tx,
},
@@ -388,7 +388,14 @@ impl HttpsConnectorWithSni {
.map_err(|err| io::Error::new(io::ErrorKind::TimedOut, err))?
}
- async fn resolve_address(address_cache: AddressCache, uri: Uri) -> io::Result<SocketAddr> {
+ /// Resolve the provided `uri` to an IP and port. If the URI contains an IP, that IP will be used.
+ /// Otherwise `address_cache` will be preferred, and `dns_resolver` will be used as a fallback.
+ /// If the URI contains a port, then that port will be used.
+ async fn resolve_address(
+ address_cache: AddressCache,
+ dns_resolver: &dyn DnsResolver,
+ uri: Uri,
+ ) -> io::Result<SocketAddr> {
const DEFAULT_PORT: u16 = 443;
let hostname = uri.host().ok_or_else(|| {
@@ -408,19 +415,13 @@ impl HttpsConnectorWithSni {
));
}
- // Use getaddrinfo as a fallback
+ // Use DNS resolution as fallback
//
- let mut addrs = GaiResolver::new()
- .call(
- Name::from_str(hostname)
- .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?,
- )
- .await
- .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
+ let addrs = dns_resolver.resolve(hostname.to_owned()).await?;
let addr = addrs
- .next()
+ .first()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Empty DNS response"))?;
- Ok(SocketAddr::new(addr.ip(), port.unwrap_or(DEFAULT_PORT)))
+ Ok(SocketAddr::new(*addr, port.unwrap_or(DEFAULT_PORT)))
}
}
@@ -455,6 +456,7 @@ impl Service<Uri> for HttpsConnectorWithSni {
#[cfg(target_os = "android")]
let socket_bypass_tx = self.socket_bypass_tx.clone();
let address_cache = self.address_cache.clone();
+ let dns_resolver = self.dns_resolver.clone();
let fut = async move {
if uri.scheme() != Some(&Scheme::HTTPS) {
@@ -465,7 +467,7 @@ impl Service<Uri> for HttpsConnectorWithSni {
}
let hostname = sni_hostname?;
- let addr = Self::resolve_address(address_cache, uri).await?;
+ let addr = Self::resolve_address(address_cache, &*dns_resolver, uri).await?;
// Loop until we have established a connection. This starts over if a new endpoint
// is selected while connecting.
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs
index 6b3ac3c951..1f47d600b3 100644
--- a/mullvad-api/src/lib.rs
+++ b/mullvad-api/src/lib.rs
@@ -1,4 +1,5 @@
#![allow(rustdoc::private_intra_doc_links)]
+use async_trait::async_trait;
#[cfg(target_os = "android")]
use futures::channel::mpsc;
#[cfg(target_os = "android")]
@@ -12,10 +13,11 @@ use std::{
cell::Cell,
collections::BTreeMap,
future::Future,
+ io,
net::{IpAddr, Ipv4Addr, SocketAddr},
ops::Deref,
path::Path,
- sync::OnceLock,
+ sync::{Arc, OnceLock},
};
use talpid_types::ErrorExt;
@@ -304,11 +306,43 @@ impl ApiEndpoint {
}
}
+#[async_trait]
+pub trait DnsResolver: 'static + Send + Sync {
+ async fn resolve(&self, host: String) -> io::Result<Vec<IpAddr>>;
+}
+
+/// DNS resolver that relies on `ToSocketAddrs` (`getaddrinfo`).
+pub struct DefaultDnsResolver;
+
+#[async_trait]
+impl DnsResolver for DefaultDnsResolver {
+ async fn resolve(&self, host: String) -> io::Result<Vec<IpAddr>> {
+ use std::net::ToSocketAddrs;
+ // Spawn a blocking thread, since `to_socket_addrs` relies on `libc::getaddrinfo`, which
+ // blocks and either has no timeout or a very long one.
+ let addrs = tokio::task::spawn_blocking(move || (host, 0).to_socket_addrs())
+ .await
+ .expect("DNS task panicked")?;
+ Ok(addrs.map(|addr| addr.ip()).collect())
+ }
+}
+
+/// DNS resolver that always returns no results
+pub struct NullDnsResolver;
+
+#[async_trait]
+impl DnsResolver for NullDnsResolver {
+ async fn resolve(&self, _host: String) -> io::Result<Vec<IpAddr>> {
+ Ok(vec![])
+ }
+}
+
/// A type that helps with the creation of API connections.
pub struct Runtime {
handle: tokio::runtime::Handle,
address_cache: AddressCache,
api_availability: availability::ApiAvailability,
+ dns_resolver: Arc<dyn DnsResolver>,
#[cfg(target_os = "android")]
socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>,
}
@@ -323,13 +357,20 @@ pub enum Error {
#[error("API availability check failed")]
ApiCheckError(#[from] availability::Error),
+
+ #[error("DNS resolution error")]
+ ResolutionFailed(#[from] std::io::Error),
}
impl Runtime {
/// Create a new `Runtime`.
- pub fn new(handle: tokio::runtime::Handle) -> Result<Self, Error> {
+ pub fn new(
+ handle: tokio::runtime::Handle,
+ dns_resolver: impl DnsResolver,
+ ) -> Result<Self, Error> {
Self::new_inner(
handle,
+ dns_resolver,
#[cfg(target_os = "android")]
None,
)
@@ -346,12 +387,14 @@ impl Runtime {
fn new_inner(
handle: tokio::runtime::Handle,
+ dns_resolver: impl DnsResolver,
#[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>,
) -> Result<Self, Error> {
Ok(Runtime {
handle,
address_cache: AddressCache::new(None)?,
api_availability: ApiAvailability::default(),
+ dns_resolver: Arc::new(dns_resolver),
#[cfg(target_os = "android")]
socket_bypass_tx,
})
@@ -360,15 +403,18 @@ impl Runtime {
/// Create a new `Runtime` using the specified directories.
/// Try to use the cache directory first, and fall back on the bundled address otherwise.
pub async fn with_cache(
+ dns_resolver: impl DnsResolver,
cache_dir: &Path,
write_changes: bool,
#[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>,
) -> Result<Self, Error> {
let handle = tokio::runtime::Handle::current();
+
#[cfg(feature = "api-override")]
if API.disable_address_cache {
return Self::new_inner(
handle,
+ dns_resolver,
#[cfg(target_os = "android")]
socket_bypass_tx,
);
@@ -402,6 +448,7 @@ impl Runtime {
handle,
address_cache,
api_availability,
+ dns_resolver: Arc::new(dns_resolver),
#[cfg(target_os = "android")]
socket_bypass_tx,
})
@@ -419,6 +466,7 @@ impl Runtime {
self.api_availability.clone(),
self.address_cache.clone(),
connection_mode_provider,
+ self.dns_resolver.clone(),
#[cfg(target_os = "android")]
socket_bypass_tx,
)
diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs
index f6098c3b49..54a32f63f9 100644
--- a/mullvad-api/src/rest.rs
+++ b/mullvad-api/src/rest.rs
@@ -6,6 +6,7 @@ use crate::{
availability::ApiAvailability,
https_client_with_sni::{HttpsConnectorWithSni, HttpsConnectorWithSniHandle},
proxy::ConnectionModeProvider,
+ DnsResolver,
};
use futures::{
channel::{mpsc, oneshot},
@@ -154,11 +155,13 @@ impl<T: ConnectionModeProvider + 'static> RequestService<T> {
api_availability: ApiAvailability,
address_cache: AddressCache,
connection_mode_provider: T,
+ dns_resolver: Arc<dyn DnsResolver>,
#[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>,
) -> RequestServiceHandle {
let (connector, connector_handle) = HttpsConnectorWithSni::new(
sni_hostname,
address_cache.clone(),
+ dns_resolver,
#[cfg(target_os = "android")]
socket_bypass_tx.clone(),
);
diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml
index 778ef02d7f..d0986fa70f 100644
--- a/mullvad-daemon/Cargo.toml
+++ b/mullvad-daemon/Cargo.toml
@@ -51,6 +51,8 @@ tokio = { workspace = true, features = ["test-util"] }
[target.'cfg(target_os="android")'.dependencies]
android_logger = "0.8"
+async-trait = "0.1"
+hickory-resolver = { workspace = true }
[target.'cfg(unix)'.dependencies]
nix = "0.23"
diff --git a/mullvad-daemon/src/android_dns.rs b/mullvad-daemon/src/android_dns.rs
new file mode 100644
index 0000000000..ed44f5dc8c
--- /dev/null
+++ b/mullvad-daemon/src/android_dns.rs
@@ -0,0 +1,49 @@
+#![cfg(target_os = "android")]
+//! See [AndroidDnsResolver].
+
+use async_trait::async_trait;
+use hickory_resolver::{
+ config::{NameServerConfigGroup, ResolverConfig, ResolverOpts},
+ TokioAsyncResolver,
+};
+use mullvad_api::DnsResolver;
+use std::{io, net::IpAddr};
+use talpid_core::connectivity_listener::ConnectivityListener;
+
+/// A non-blocking DNS resolver. The default resolver uses `getaddrinfo`, which often prevents the
+/// tokio runtime from being dropped, since it waits indefinitely on blocking threads. This is
+/// particularly bad on Android, so we use a non-blocking resolver instead.
+pub struct AndroidDnsResolver {
+ connectivity_listener: ConnectivityListener,
+}
+
+impl AndroidDnsResolver {
+ pub fn new(connectivity_listener: ConnectivityListener) -> Self {
+ Self {
+ connectivity_listener,
+ }
+ }
+}
+
+#[async_trait]
+impl DnsResolver for AndroidDnsResolver {
+ async fn resolve(&self, host: String) -> io::Result<Vec<IpAddr>> {
+ let ips = self
+ .connectivity_listener
+ .current_dns_servers()
+ .map_err(|err| {
+ io::Error::other(format!("Failed to retrieve current servers: {err}"))
+ })?;
+ let group = NameServerConfigGroup::from_ips_clear(&ips, 53, false);
+
+ let config = ResolverConfig::from_parts(None, vec![], group);
+ let resolver = TokioAsyncResolver::tokio(config, ResolverOpts::default());
+
+ let lookup = resolver
+ .lookup_ip(host)
+ .await
+ .map_err(|err| io::Error::other(format!("lookup_ip failed: {err}")))?;
+
+ Ok(lookup.into_iter().collect())
+ }
+}
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 8299e1d20c..0f2914fdff 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -3,6 +3,7 @@
mod access_method;
pub mod account_history;
+mod android_dns;
mod api;
mod api_address_updater;
#[cfg(not(target_os = "android"))]
@@ -38,6 +39,8 @@ use futures::{
};
use geoip::GeoIpHandler;
use management_interface::ManagementInterfaceServer;
+#[cfg(not(target_os = "android"))]
+use mullvad_api::DefaultDnsResolver;
use mullvad_relay_selector::{RelaySelector, SelectorConfig};
#[cfg(target_os = "android")]
use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
@@ -91,6 +94,9 @@ use talpid_types::{
};
use tokio::io;
+#[cfg(target_os = "android")]
+use talpid_core::connectivity_listener::ConnectivityListener;
+
/// Delay between generating a new WireGuard key and reconnecting
const WG_RECONNECT_DELAY: Duration = Duration::from_secs(4 * 60);
@@ -604,8 +610,23 @@ impl Daemon {
let (internal_event_tx, internal_event_rx) = daemon_command_channel.destructure();
+ #[cfg(target_os = "android")]
+ let connectivity_listener = ConnectivityListener::new(android_context.clone())
+ .inspect_err(|error| {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to start connectivity listener")
+ );
+ })
+ .map_err(|_| Error::DaemonUnavailable)?;
+
mullvad_api::proxy::ApiConnectionMode::try_delete_cache(&cache_dir).await;
let api_runtime = mullvad_api::Runtime::with_cache(
+ // FIXME: clone is bad (single sender)
+ #[cfg(target_os = "android")]
+ android_dns::AndroidDnsResolver::new(connectivity_listener.clone()),
+ #[cfg(not(target_os = "android"))]
+ DefaultDnsResolver,
&cache_dir,
true,
#[cfg(target_os = "android")]
@@ -777,6 +798,8 @@ impl Daemon {
volume_update_rx,
#[cfg(target_os = "android")]
android_context,
+ #[cfg(target_os = "android")]
+ connectivity_listener,
#[cfg(target_os = "linux")]
tunnel_state_machine::LinuxNetworkingIdentifiers {
fwmark: mullvad_types::TUNNEL_FWMARK,
diff --git a/mullvad-encrypted-dns-proxy/Cargo.toml b/mullvad-encrypted-dns-proxy/Cargo.toml
index e5ed53056c..1326337a66 100644
--- a/mullvad-encrypted-dns-proxy/Cargo.toml
+++ b/mullvad-encrypted-dns-proxy/Cargo.toml
@@ -13,7 +13,7 @@ workspace = true
[dependencies]
tokio = { workspace = true, features = [ "macros" ] }
log = { workspace = true }
-hickory-resolver = { version = "0.24.1", features = [ "dns-over-https-rustls" ]}
+hickory-resolver = { workspace = true, features = [ "dns-over-https-rustls" ]}
serde = { workspace = true, features = ["derive"] }
webpki-roots = "0.25.0"
rustls = "0.21"
diff --git a/mullvad-problem-report/src/lib.rs b/mullvad-problem-report/src/lib.rs
index 270de55f95..91b790e5f6 100644
--- a/mullvad-problem-report/src/lib.rs
+++ b/mullvad-problem-report/src/lib.rs
@@ -1,4 +1,4 @@
-use mullvad_api::proxy::ApiConnectionMode;
+use mullvad_api::{proxy::ApiConnectionMode, NullDnsResolver};
use regex::Regex;
use std::{
borrow::Cow,
@@ -292,6 +292,7 @@ async fn send_problem_report_inner(
) -> Result<(), Error> {
let metadata = ProblemReport::parse_metadata(report_content).unwrap_or_else(metadata::collect);
let api_runtime = mullvad_api::Runtime::with_cache(
+ NullDnsResolver,
cache_dir,
false,
#[cfg(target_os = "android")]
diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs
index d3dfd6de8a..4a444aa63c 100644
--- a/mullvad-setup/src/main.rs
+++ b/mullvad-setup/src/main.rs
@@ -1,7 +1,7 @@
use clap::Parser;
use std::{path::PathBuf, process, str::FromStr, sync::LazyLock, time::Duration};
-use mullvad_api::{proxy::ApiConnectionMode, DEVICE_NOT_FOUND};
+use mullvad_api::{proxy::ApiConnectionMode, NullDnsResolver, DEVICE_NOT_FOUND};
use mullvad_management_interface::MullvadProxyClient;
use mullvad_types::version::ParsedAppVersion;
use talpid_core::firewall::{self, Firewall};
@@ -152,7 +152,7 @@ async fn remove_device() -> Result<(), Error> {
.await
.map_err(Error::ReadDeviceCacheError)?;
if let Some(device) = state.into_device() {
- let api_runtime = mullvad_api::Runtime::with_cache(&cache_path, false)
+ let api_runtime = mullvad_api::Runtime::with_cache(NullDnsResolver, &cache_path, false)
.await
.map_err(Error::RpcInitializationError)?;
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 690d5797cc..7ec3fd20f2 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -50,8 +50,8 @@ duct = "0.13"
pfctl = "0.6.1"
subslice = "0.2"
system-configuration = "0.5.1"
-hickory-proto = "0.24.1"
-hickory-server = { version = "0.24.1", features = ["resolver"] }
+hickory-proto = { workspace = true }
+hickory-server = { workspace = true, features = ["resolver"] }
talpid-platform-metadata = { path = "../talpid-platform-metadata" }
pcap = { version = "2.1", features = ["capture-stream"] }
pnet_packet = "0.34"
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;
diff --git a/test/Cargo.lock b/test/Cargo.lock
index 24852c3356..941a0a08da 100644
--- a/test/Cargo.lock
+++ b/test/Cargo.lock
@@ -300,9 +300,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "blake3"
@@ -1324,9 +1324,9 @@ dependencies = [
[[package]]
name = "httparse"
-version = "1.8.0"
+version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
name = "httpdate"
@@ -1702,7 +1702,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"libc",
]
@@ -1867,13 +1867,25 @@ dependencies = [
]
[[package]]
+name = "mio"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "mio-serial"
version = "5.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20a4c60ca5c9c0e114b3bd66ff4aa5f9b2b175442be51ca6c4365d687a97a2ac"
dependencies = [
"log",
- "mio",
+ "mio 0.8.11",
"nix 0.26.4",
"serialport",
"winapi",
@@ -1883,6 +1895,7 @@ dependencies = [
name = "mullvad-api"
version = "0.0.0"
dependencies = [
+ "async-trait",
"cbindgen",
"chrono",
"futures",
@@ -2042,7 +2055,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2061,7 +2074,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"crossbeam-channel",
"filetime",
"fsevent-sys",
@@ -2069,7 +2082,7 @@ dependencies = [
"kqueue",
"libc",
"log",
- "mio",
+ "mio 0.8.11",
"walkdir",
"windows-sys 0.48.0",
]
@@ -2110,16 +2123,6 @@ dependencies = [
]
[[package]]
-name = "num_cpus"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
-[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2883,7 +2886,7 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"errno 0.3.8",
"libc",
"linux-raw-sys",
@@ -3079,7 +3082,7 @@ version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"cfg-if",
"core-foundation-sys",
"io-kit-sys",
@@ -3624,28 +3627,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.37.0"
+version = "1.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
+checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
dependencies = [
"backtrace",
"bytes",
"libc",
- "mio",
- "num_cpus",
+ "mio 1.0.2",
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.5.6",
"tokio-macros",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
-version = "2.2.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
@@ -4525,9 +4527,9 @@ dependencies = [
[[package]]
name = "zeroize"
-version = "1.7.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
dependencies = [
"zeroize_derive",
]
diff --git a/test/test-manager/src/tests/account.rs b/test/test-manager/src/tests/account.rs
index 7fe14ae58e..45151070a9 100644
--- a/test/test-manager/src/tests/account.rs
+++ b/test/test-manager/src/tests/account.rs
@@ -295,8 +295,11 @@ pub async fn new_device_client() -> anyhow::Result<DevicesProxy> {
..api_endpoint
});
- let api = mullvad_api::Runtime::new(tokio::runtime::Handle::current())
- .expect("failed to create api runtime");
+ let api = mullvad_api::Runtime::new(
+ tokio::runtime::Handle::current(),
+ mullvad_api::DefaultDnsResolver,
+ )
+ .expect("failed to create api runtime");
let rest_handle = api.mullvad_rest_handle(ApiConnectionMode::Direct.into_provider());
Ok(DevicesProxy::new(rest_handle))
}