Skip to content

Commit 6ffbd87

Browse files
committed
Merge branch 'android-simplify-daemon-spawn'
2 parents 5809c7c + 667bbf3 commit 6ffbd87

File tree

13 files changed

+338
-566
lines changed

13 files changed

+338
-566
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,19 @@
11
package net.mullvad.mullvadvpn.service
22

3-
import android.annotation.SuppressLint
4-
import android.content.Context
5-
import java.io.File
6-
import kotlinx.coroutines.Dispatchers
7-
import kotlinx.coroutines.async
8-
import kotlinx.coroutines.channels.Channel
9-
import kotlinx.coroutines.runBlocking
10-
import kotlinx.coroutines.withContext
113
import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpoint
12-
import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration
13-
import net.mullvad.mullvadvpn.service.migration.MigrateSplitTunneling
14-
15-
private const val RELAYS_FILE = "relays.json"
16-
17-
@SuppressLint("SdCardPath")
18-
class MullvadDaemon(
19-
vpnService: MullvadVpnService,
20-
rpcSocketFile: File,
21-
apiEndpointConfiguration: ApiEndpointConfiguration,
22-
migrateSplitTunneling: MigrateSplitTunneling
23-
) {
24-
// Used by JNI
25-
@Suppress("ProtectedMemberInFinalClass") protected var daemonInterfaceAddress = 0L
26-
27-
private val shutdownSignal = Channel<Unit>()
284

5+
object MullvadDaemon {
296
init {
307
System.loadLibrary("mullvad_jni")
31-
32-
prepareFiles(vpnService)
33-
34-
migrateSplitTunneling.migrate()
35-
36-
initialize(
37-
vpnService = vpnService,
38-
rpcSocketPath = rpcSocketFile.absolutePath,
39-
filesDirectory = vpnService.filesDir.absolutePath,
40-
cacheDirectory = vpnService.cacheDir.absolutePath,
41-
apiEndpoint = apiEndpointConfiguration.apiEndpoint()
42-
)
43-
}
44-
45-
suspend fun shutdown() =
46-
withContext(Dispatchers.IO) {
47-
val shutdownSignal = async { shutdownSignal.receive() }
48-
shutdown(daemonInterfaceAddress)
49-
shutdownSignal.await()
50-
deinitialize()
51-
}
52-
53-
private fun prepareFiles(context: Context) {
54-
val shouldOverwriteRelayList =
55-
lastUpdatedTime(context) > File(context.filesDir, RELAYS_FILE).lastModified()
56-
57-
FileResourceExtractor(context).apply { extract(RELAYS_FILE, shouldOverwriteRelayList) }
588
}
599

60-
private fun lastUpdatedTime(context: Context): Long =
61-
context.packageManager.getPackageInfo(context.packageName, 0).lastUpdateTime
62-
63-
// Used by JNI
64-
@Suppress("unused")
65-
private fun notifyDaemonStopped() {
66-
runBlocking {
67-
shutdownSignal.send(Unit)
68-
shutdownSignal.close()
69-
}
70-
}
71-
72-
private external fun initialize(
10+
external fun initialize(
7311
vpnService: MullvadVpnService,
7412
rpcSocketPath: String,
7513
filesDirectory: String,
7614
cacheDirectory: String,
7715
apiEndpoint: ApiEndpoint?
7816
)
7917

80-
private external fun deinitialize()
81-
82-
private external fun shutdown(daemonInterfaceAddress: Long)
18+
external fun shutdown()
8319
}

android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt

+38-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.mullvad.mullvadvpn.service
22

33
import android.app.KeyguardManager
4+
import android.content.Context
45
import android.content.Intent
56
import android.os.Binder
67
import android.os.Build
@@ -18,6 +19,7 @@ import kotlinx.coroutines.flow.first
1819
import kotlinx.coroutines.flow.update
1920
import kotlinx.coroutines.launch
2021
import kotlinx.coroutines.runBlocking
22+
import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
2123
import net.mullvad.mullvadvpn.lib.common.constant.GRPC_SOCKET_FILE_NAMED_ARGUMENT
2224
import net.mullvad.mullvadvpn.lib.common.constant.KEY_CONNECT_ACTION
2325
import net.mullvad.mullvadvpn.lib.common.constant.KEY_DISCONNECT_ACTION
@@ -40,12 +42,13 @@ import org.koin.android.ext.android.getKoin
4042
import org.koin.core.context.loadKoinModules
4143
import org.koin.core.qualifier.named
4244

45+
private const val RELAYS_FILE = "relays.json"
46+
4347
class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
4448
private val _shouldBeOnForeground = MutableStateFlow(false)
4549
override val shouldBeOnForeground: StateFlow<Boolean> = _shouldBeOnForeground
4650

4751
private lateinit var keyguardManager: KeyguardManager
48-
private lateinit var daemonInstance: MullvadDaemon
4952

5053
private lateinit var apiEndpointConfiguration: ApiEndpointConfiguration
5154
private lateinit var managementService: ManagementService
@@ -90,15 +93,11 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
9093
// with intent from API)
9194
lifecycleScope.launch(context = Dispatchers.IO) {
9295
managementService.start()
93-
daemonInstance =
94-
MullvadDaemon(
95-
vpnService = this@MullvadVpnService,
96-
rpcSocketFile = rpcSocketFile,
97-
apiEndpointConfiguration =
98-
intentProvider.getLatestIntent()?.getApiEndpointConfigurationExtras()
99-
?: apiEndpointConfiguration,
100-
migrateSplitTunneling = migrateSplitTunneling
101-
)
96+
97+
prepareFiles(this@MullvadVpnService)
98+
migrateSplitTunneling.migrate()
99+
100+
startDaemon()
102101
}
103102
}
104103

@@ -146,6 +145,24 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
146145
return super.onBind(intent) ?: emptyBinder()
147146
}
148147

148+
private fun startDaemon() {
149+
val apiEndpointConfiguration =
150+
if (Build.TYPE == BuildTypes.DEBUG) {
151+
intentProvider.getLatestIntent()?.getApiEndpointConfigurationExtras()
152+
?: apiEndpointConfiguration
153+
} else {
154+
apiEndpointConfiguration
155+
}
156+
157+
MullvadDaemon.initialize(
158+
vpnService = this@MullvadVpnService,
159+
rpcSocketPath = rpcSocketFile.absolutePath,
160+
filesDirectory = filesDir.absolutePath,
161+
cacheDirectory = cacheDir.absolutePath,
162+
apiEndpoint = apiEndpointConfiguration.apiEndpoint()
163+
)
164+
}
165+
149166
private fun emptyBinder() =
150167
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
151168
Binder(this.toString())
@@ -193,7 +210,7 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
193210
managementService.stop()
194211

195212
// Shutting down the daemon gracefully
196-
runBlocking { daemonInstance.shutdown() }
213+
MullvadDaemon.shutdown()
197214
super.onDestroy()
198215
}
199216

@@ -202,6 +219,16 @@ class MullvadVpnService : TalpidVpnService(), ShouldBeOnForegroundProvider {
202219
return this?.action == SERVICE_INTERFACE
203220
}
204221

222+
private fun prepareFiles(context: Context) {
223+
val shouldOverwriteRelayList =
224+
lastUpdatedTime(context) > File(context.filesDir, RELAYS_FILE).lastModified()
225+
226+
FileResourceExtractor(context).apply { extract(RELAYS_FILE, shouldOverwriteRelayList) }
227+
}
228+
229+
private fun lastUpdatedTime(context: Context): Long =
230+
context.packageManager.getPackageInfo(context.packageName, 0).lastUpdateTime
231+
205232
companion object {
206233
init {
207234
System.loadLibrary("mullvad_jni")

mullvad-daemon/src/access_method.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub enum Error {
3030

3131
impl<L> Daemon<L>
3232
where
33-
L: EventListener + Clone + Send + 'static,
33+
L: EventListener,
3434
{
3535
/// Add a [`AccessMethod`] to the daemon's settings.
3636
///

mullvad-daemon/src/custom_list.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use talpid_types::net::TunnelType;
88

99
impl<L> Daemon<L>
1010
where
11-
L: EventListener + Clone + Send + 'static,
11+
L: EventListener,
1212
{
1313
/// Create a new custom list.
1414
///

mullvad-daemon/src/lib.rs

+25-43
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use api::AccessMethodEvent;
3333
use device::{AccountEvent, PrivateAccountAndDevice, PrivateDeviceEvent};
3434
use futures::{
3535
channel::{mpsc, oneshot},
36-
future::{abortable, AbortHandle, Future, LocalBoxFuture},
36+
future::{abortable, AbortHandle, Future},
3737
StreamExt,
3838
};
3939
use geoip::GeoIpHandler;
@@ -467,7 +467,7 @@ impl DaemonCommandChannel {
467467
}
468468
}
469469

470-
#[derive(Clone)]
470+
#[derive(Debug, Clone)]
471471
pub struct DaemonCommandSender(Arc<mpsc::UnboundedSender<InternalDaemonEvent>>);
472472

473473
impl DaemonCommandSender {
@@ -540,7 +540,7 @@ where
540540
}
541541

542542
/// Trait representing something that can broadcast daemon events.
543-
pub trait EventListener {
543+
pub trait EventListener: Clone + Send + Sync + 'static {
544544
/// Notify that the tunnel state changed.
545545
fn notify_new_state(&self, new_state: TunnelState);
546546

@@ -585,7 +585,7 @@ pub struct Daemon<L: EventListener> {
585585
relay_selector: RelaySelector,
586586
relay_list_updater: RelayListUpdaterHandle,
587587
parameters_generator: tunnel::ParametersGenerator,
588-
shutdown_tasks: Vec<Pin<Box<dyn Future<Output = ()>>>>,
588+
shutdown_tasks: Vec<Pin<Box<dyn Future<Output = ()> + Send + Sync>>>,
589589
tunnel_state_machine_handle: TunnelStateMachineHandle,
590590
#[cfg(target_os = "windows")]
591591
volume_update_tx: mpsc::UnboundedSender<()>,
@@ -594,7 +594,7 @@ pub struct Daemon<L: EventListener> {
594594

595595
impl<L> Daemon<L>
596596
where
597-
L: EventListener + Clone + Send + 'static,
597+
L: EventListener,
598598
{
599599
pub async fn start(
600600
log_dir: Option<PathBuf>,
@@ -897,46 +897,27 @@ where
897897
/// Destroy daemon safely, by dropping all objects in the correct order, waiting for them to
898898
/// be destroyed, and executing shutdown tasks
899899
async fn finalize(self) {
900-
let (event_listener, shutdown_tasks, api_runtime, tunnel_state_machine_handle) =
901-
self.shutdown();
902-
for future in shutdown_tasks {
903-
future.await;
904-
}
905-
906-
tunnel_state_machine_handle.try_join().await;
907-
908-
drop(event_listener);
909-
drop(api_runtime);
910-
}
911-
912-
/// Shuts down the daemon without shutting down the underlying event listener and the shutdown
913-
/// callbacks
914-
fn shutdown<'a>(
915-
self,
916-
) -> (
917-
L,
918-
Vec<LocalBoxFuture<'a, ()>>,
919-
mullvad_api::Runtime,
920-
TunnelStateMachineHandle,
921-
) {
922900
let Daemon {
923901
event_listener,
924-
mut shutdown_tasks,
902+
shutdown_tasks,
925903
api_runtime,
926904
tunnel_state_machine_handle,
927905
target_state,
928906
account_manager,
929907
..
930908
} = self;
931-
shutdown_tasks.push(Box::pin(target_state.finalize()));
932-
shutdown_tasks.push(Box::pin(account_manager.shutdown()));
933909

934-
(
935-
event_listener,
936-
shutdown_tasks,
937-
api_runtime,
938-
tunnel_state_machine_handle,
939-
)
910+
for future in shutdown_tasks {
911+
future.await;
912+
}
913+
914+
target_state.finalize().await;
915+
account_manager.shutdown().await;
916+
917+
tunnel_state_machine_handle.try_join().await;
918+
919+
drop(event_listener);
920+
drop(api_runtime);
940921
}
941922

942923
async fn handle_event(&mut self, event: InternalDaemonEvent) -> bool {
@@ -1686,7 +1667,7 @@ where
16861667

16871668
#[cfg(not(target_os = "android"))]
16881669
async fn on_factory_reset(&mut self, tx: ResponseTx<(), Error>) {
1689-
let mut last_error = Ok(());
1670+
let mut last_error = None;
16901671

16911672
if let Err(error) = self.account_manager.logout().await {
16921673
log::error!(
@@ -1700,12 +1681,12 @@ where
17001681
"{}",
17011682
error.display_chain_with_msg("Failed to clear account history")
17021683
);
1703-
last_error = Err(Error::FactoryResetError("Failed to clear account history"));
1684+
last_error = Some("Failed to clear account history");
17041685
}
17051686

17061687
if let Err(e) = self.settings.reset().await {
17071688
log::error!("Failed to reset settings: {}", e);
1708-
last_error = Err(Error::FactoryResetError("Failed to reset settings"));
1689+
last_error = Some("Failed to reset settings");
17091690
}
17101691

17111692
// Shut the daemon down.
@@ -1717,11 +1698,12 @@ where
17171698
"{}",
17181699
e.display_chain_with_msg("Failed to clear cache and log directories")
17191700
);
1720-
last_error = Err(Error::FactoryResetError(
1721-
"Failed to clear cache and log directories",
1722-
));
1701+
last_error = Some("Failed to clear cache and log directories");
17231702
}
1724-
Self::oneshot_send(tx, last_error, "factory_reset response");
1703+
let result = last_error
1704+
.map(|error| Err(Error::FactoryResetError(error)))
1705+
.unwrap_or(Ok(()));
1706+
Self::oneshot_send(tx, result, "factory_reset response");
17251707
}));
17261708
}
17271709

mullvad-daemon/src/settings/mod.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ pub struct SettingsPersister {
9494
settings: Settings,
9595
path: PathBuf,
9696
#[allow(clippy::type_complexity)]
97-
on_change_listeners: Vec<Box<dyn Fn(&Settings)>>,
97+
on_change_listeners: Vec<Box<dyn Fn(&Settings) + Send + Sync>>,
9898
}
9999

100100
pub type MadeChanges = bool;
@@ -353,7 +353,10 @@ impl SettingsPersister {
353353
}
354354
}
355355

356-
pub fn register_change_listener(&mut self, change_listener: impl Fn(&Settings) + 'static) {
356+
pub fn register_change_listener(
357+
&mut self,
358+
change_listener: impl Fn(&Settings) + Send + Sync + 'static,
359+
) {
357360
self.on_change_listeners.push(Box::new(change_listener));
358361
}
359362

mullvad-jni/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ api-override = ["mullvad-api/api-override"]
1818
crate-type = ["cdylib"]
1919

2020
[target.'cfg(target_os = "android")'.dependencies]
21+
tokio = { workspace = true, features = ["rt"] }
22+
2123
thiserror = { workspace = true }
2224
futures = "0.3"
2325
ipnetwork = "0.16"

0 commit comments

Comments
 (0)