summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-06-19 15:56:40 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-06-19 15:56:40 +0200
commit9f2acb70e2677cd8c3f7eeda56dec56c07f65ad4 (patch)
tree6d564f0b0585219cc49966ea2dc86c31b251fc87
parent3edc071e807ba1b9221cf3b972f200209341a402 (diff)
parent92dd8dcdb8a0ab512c52171621f0440cfa9c3136 (diff)
downloadmullvadvpn-9f2acb70e2677cd8c3f7eeda56dec56c07f65ad4.tar.xz
mullvadvpn-9f2acb70e2677cd8c3f7eeda56dec56c07f65ad4.zip
Merge branch 'win-add-start-service-mullvad-setup'
-rw-r--r--Cargo.lock2
-rw-r--r--desktop/packages/mullvad-vpn/tasks/distribution.js4
-rw-r--r--mullvad-daemon/src/lib.rs6
-rw-r--r--mullvad-daemon/src/system_service.rs8
-rw-r--r--mullvad-setup/Cargo.toml7
-rw-r--r--mullvad-setup/src/main.rs28
-rw-r--r--mullvad-setup/src/service.rs67
7 files changed, 119 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2e0459345e..5aa2d3b30d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3203,6 +3203,8 @@ dependencies = [
"talpid-types",
"thiserror 2.0.9",
"tokio",
+ "windows-service",
+ "windows-sys 0.52.0",
]
[[package]]
diff --git a/desktop/packages/mullvad-vpn/tasks/distribution.js b/desktop/packages/mullvad-vpn/tasks/distribution.js
index a4b3dc84f5..45f9e8f181 100644
--- a/desktop/packages/mullvad-vpn/tasks/distribution.js
+++ b/desktop/packages/mullvad-vpn/tasks/distribution.js
@@ -137,6 +137,10 @@ function newConfig() {
to: '.',
},
{ from: distAssets(path.join('${env.DIST_SUBDIR}', 'mullvad-daemon.exe')), to: '.' },
+ {
+ from: distAssets(path.join('${env.DIST_SUBDIR}', 'mullvad-setup.exe')),
+ to: '.',
+ },
{ from: distAssets(path.join('${env.DIST_SUBDIR}', 'talpid_openvpn_plugin.dll')), to: '.' },
{
from: root(
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 70db828aa3..f73ab6927c 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -101,6 +101,12 @@ use tokio::io;
#[cfg(target_os = "android")]
use talpid_core::connectivity_listener::ConnectivityListener;
+#[cfg(target_os = "windows")]
+pub mod service {
+ pub const SERVICE_NAME: &str = "MullvadVPN";
+ pub const SERVICE_DISPLAY_NAME: &str = "Mullvad VPN Service";
+}
+
/// Delay between generating a new WireGuard key and reconnecting
const WG_RECONNECT_DELAY: Duration = Duration::from_secs(4 * 60);
diff --git a/mullvad-daemon/src/system_service.rs b/mullvad-daemon/src/system_service.rs
index 6b1f521474..7e238d3bc6 100644
--- a/mullvad-daemon/src/system_service.rs
+++ b/mullvad-daemon/src/system_service.rs
@@ -1,7 +1,11 @@
#![allow(clippy::undocumented_unsafe_blocks)] // Remove me if you dare.
use crate::cli;
-use mullvad_daemon::{runtime::new_multi_thread, DaemonShutdownHandle};
+use mullvad_daemon::{
+ runtime::new_multi_thread,
+ service::{SERVICE_DISPLAY_NAME, SERVICE_NAME},
+ DaemonShutdownHandle,
+};
use std::{
env,
ffi::{c_void, OsString},
@@ -33,8 +37,6 @@ use windows_sys::Win32::{
},
};
-static SERVICE_NAME: &str = "MullvadVPN";
-static SERVICE_DISPLAY_NAME: &str = "Mullvad VPN Service";
static SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
const SERVICE_RECOVERY_LAST_RESTART_DELAY: Duration = Duration::from_secs(60 * 10);
diff --git a/mullvad-setup/Cargo.toml b/mullvad-setup/Cargo.toml
index 0b526b8ff6..7c377734d8 100644
--- a/mullvad-setup/Cargo.toml
+++ b/mullvad-setup/Cargo.toml
@@ -31,3 +31,10 @@ mullvad-version = { path = "../mullvad-version" }
talpid-core = { path = "../talpid-core" }
talpid-future = { path = "../talpid-future" }
talpid-types = { path = "../talpid-types" }
+
+[target.'cfg(windows)'.dependencies]
+windows-service = "0.6.0"
+
+[target.'cfg(windows)'.dependencies.windows-sys]
+workspace = true
+features = ["Win32_Foundation"]
diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs
index 166364cf92..1b150b533f 100644
--- a/mullvad-setup/src/main.rs
+++ b/mullvad-setup/src/main.rs
@@ -7,6 +7,9 @@ use talpid_core::firewall::{self, Firewall};
use talpid_future::retry::{retry_future, ConstantInterval};
use talpid_types::ErrorExt;
+#[cfg(target_os = "windows")]
+mod service;
+
static APP_VERSION: LazyLock<Version> =
LazyLock::new(|| Version::from_str(mullvad_version::VERSION).unwrap());
@@ -63,6 +66,26 @@ pub enum Error {
#[error("Cannot parse the version string")]
ParseVersionStringError,
+
+ #[cfg(target_os = "windows")]
+ #[error("Failed to start system service")]
+ StartService(#[source] windows_service::Error),
+
+ #[cfg(target_os = "windows")]
+ #[error("Failed to query system service")]
+ QueryServiceStatus(#[source] windows_service::Error),
+
+ #[cfg(target_os = "windows")]
+ #[error("Starting system service timed out")]
+ StartServiceTimeout,
+
+ #[cfg(target_os = "windows")]
+ #[error("Failed to open service")]
+ OpenService(#[source] windows_service::Error),
+
+ #[cfg(target_os = "windows")]
+ #[error("Failed to open service control manager")]
+ OpenServiceControlManager(#[source] windows_service::Error),
}
#[derive(Debug, Parser)]
@@ -86,6 +109,9 @@ enum Cli {
#[arg(required = true)]
old_version: String,
},
+ /// Start the Mullvad daemon service
+ #[cfg(target_os = "windows")]
+ StartService,
}
#[tokio::main]
@@ -103,6 +129,8 @@ async fn main() {
Err(error) => Err(error),
}
}
+ #[cfg(target_os = "windows")]
+ Cli::StartService => service::start().await,
};
if let Err(e) = result {
diff --git a/mullvad-setup/src/service.rs b/mullvad-setup/src/service.rs
new file mode 100644
index 0000000000..cda69bc06e
--- /dev/null
+++ b/mullvad-setup/src/service.rs
@@ -0,0 +1,67 @@
+use std::{
+ ffi::{OsStr, OsString},
+ time::Duration,
+};
+
+use super::Error;
+
+use mullvad_daemon::service::{SERVICE_DISPLAY_NAME, SERVICE_NAME};
+use windows_service::{
+ service::{Service, ServiceAccess, ServiceState},
+ service_manager::{ServiceManager, ServiceManagerAccess},
+};
+
+use windows_sys::Win32::Foundation::ERROR_SERVICE_ALREADY_RUNNING;
+
+const WAIT_STATUS_TIMEOUT: Duration = Duration::from_secs(8);
+
+/// Start Mullvad VPN service and wait until it is running
+pub async fn start() -> Result<(), Error> {
+ tokio::task::spawn_blocking(start_and_wait_for_service)
+ .await
+ .unwrap()
+}
+
+fn start_and_wait_for_service() -> Result<(), Error> {
+ println!("Starting {SERVICE_DISPLAY_NAME}...");
+
+ let scm = ServiceManager::local_computer(
+ None::<OsString>,
+ ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE,
+ )
+ .map_err(Error::OpenServiceControlManager)?;
+
+ let service = scm
+ .open_service(SERVICE_NAME, ServiceAccess::all())
+ .map_err(Error::OpenService)?;
+
+ if let Err(error) = service.start::<&OsStr>(&[]) {
+ if let windows_service::Error::Winapi(error) = &error {
+ if error.raw_os_error() == Some(ERROR_SERVICE_ALREADY_RUNNING as i32) {
+ return Ok(());
+ }
+ }
+ return Err(Error::StartService(error));
+ }
+
+ wait_for_status(&service, ServiceState::Running)
+}
+
+fn wait_for_status(service: &Service, target_state: ServiceState) -> Result<(), Error> {
+ let initial_time = std::time::Instant::now();
+ loop {
+ let status = service.query_status().map_err(Error::QueryServiceStatus)?;
+
+ if status.current_state == target_state {
+ break;
+ }
+
+ if initial_time.elapsed() >= WAIT_STATUS_TIMEOUT {
+ return Err(Error::StartServiceTimeout);
+ }
+
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ }
+
+ Ok(())
+}