diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-06-19 15:56:40 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-06-19 15:56:40 +0200 |
| commit | 9f2acb70e2677cd8c3f7eeda56dec56c07f65ad4 (patch) | |
| tree | 6d564f0b0585219cc49966ea2dc86c31b251fc87 | |
| parent | 3edc071e807ba1b9221cf3b972f200209341a402 (diff) | |
| parent | 92dd8dcdb8a0ab512c52171621f0440cfa9c3136 (diff) | |
| download | mullvadvpn-9f2acb70e2677cd8c3f7eeda56dec56c07f65ad4.tar.xz mullvadvpn-9f2acb70e2677cd8c3f7eeda56dec56c07f65ad4.zip | |
Merge branch 'win-add-start-service-mullvad-setup'
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | desktop/packages/mullvad-vpn/tasks/distribution.js | 4 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 6 | ||||
| -rw-r--r-- | mullvad-daemon/src/system_service.rs | 8 | ||||
| -rw-r--r-- | mullvad-setup/Cargo.toml | 7 | ||||
| -rw-r--r-- | mullvad-setup/src/main.rs | 28 | ||||
| -rw-r--r-- | mullvad-setup/src/service.rs | 67 |
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(()) +} |
