//! Client that returns and takes mullvad types as arguments instead of prost-generated types use crate::types; #[cfg(not(target_os = "android"))] use futures::{Stream, StreamExt}; #[cfg(all(daita, not(target_os = "android")))] use mullvad_types::wireguard::DaitaSettings; use mullvad_types::{ access_method::AccessMethodSetting, device::{DeviceEvent, RemoveDeviceEvent}, relay_list::RelayList, settings::Settings, states::TunnelState, version::AppVersionInfo, }; #[cfg(not(target_os = "android"))] use mullvad_types::{ access_method::{self, AccessMethod}, account::{AccountData, AccountNumber, VoucherSubmission}, custom_list::{CustomList, Id}, device::{Device, DeviceId, DeviceState}, features::FeatureIndicators, relay_constraints::{AllowedIps, ObfuscationSettings, RelayOverride, RelaySettings}, relay_list::BridgeList, settings::DnsOptions, wireguard::{PublicKey, QuantumResistantState, RotationInterval}, }; use std::net::IpAddr; #[cfg(not(target_os = "android"))] use std::{path::Path, str::FromStr}; #[cfg(target_os = "windows")] use talpid_types::split_tunnel::ExcludedProcess; #[cfg(not(target_os = "android"))] use tonic::{Code, Status}; type Error = super::Error; pub type Result = std::result::Result; #[cfg(not(target_os = "android"))] #[derive(Debug, Clone)] pub struct MullvadProxyClient(crate::ManagementServiceClient); /// All events that can happen in the daemon which clients should react to. #[derive(Debug)] pub enum DaemonEvent { TunnelState(TunnelState), Settings(Settings), RelayList(RelayList), AppVersionInfo(AppVersionInfo), Device(DeviceEvent), RemoveDevice(RemoveDeviceEvent), NewAccessMethod(AccessMethodSetting), LeakDetected(LeakInfo), } impl TryFrom for DaemonEvent { type Error = Error; fn try_from(value: types::daemon_event::Event) -> Result { match value { types::daemon_event::Event::TunnelState(state) => TunnelState::try_from(state) .map(DaemonEvent::TunnelState) .map_err(Error::InvalidResponse), types::daemon_event::Event::Settings(settings) => Settings::try_from(settings) .map(DaemonEvent::Settings) .map_err(Error::InvalidResponse), types::daemon_event::Event::RelayList(list) => RelayList::try_from(list) .map(DaemonEvent::RelayList) .map_err(Error::InvalidResponse), types::daemon_event::Event::VersionInfo(info) => AppVersionInfo::try_from(info) .map(DaemonEvent::AppVersionInfo) .map_err(Error::InvalidResponse), types::daemon_event::Event::Device(event) => DeviceEvent::try_from(event) .map(DaemonEvent::Device) .map_err(Error::InvalidResponse), types::daemon_event::Event::RemoveDevice(event) => RemoveDeviceEvent::try_from(event) .map(DaemonEvent::RemoveDevice) .map_err(Error::InvalidResponse), types::daemon_event::Event::NewAccessMethod(event) => { AccessMethodSetting::try_from(event) .map(DaemonEvent::NewAccessMethod) .map_err(Error::InvalidResponse) } types::daemon_event::Event::LeakInfo(leak) => { LeakInfo::try_from(leak).map(DaemonEvent::LeakDetected) } } } } #[cfg(not(target_os = "android"))] impl MullvadProxyClient { pub async fn new() -> Result { #[expect(deprecated)] crate::new_management_service_client().await.map(Self) } pub fn from_rpc_client(client: crate::ManagementServiceClient) -> Self { Self(client) } pub async fn connect_tunnel(&mut self) -> Result { Ok(self.0.connect_tunnel(()).await?.into_inner()) } pub async fn disconnect_tunnel(&mut self, source: &str) -> Result { Ok(self .0 .disconnect_tunnel(source.to_owned()) .await? .into_inner()) } pub async fn reconnect_tunnel(&mut self) -> Result { Ok(self.0.reconnect_tunnel(()).await?.into_inner()) } pub async fn get_tunnel_state(&mut self) -> Result { let state = self.0.get_tunnel_state(()).await?.into_inner(); TunnelState::try_from(state).map_err(Error::InvalidResponse) } pub async fn events_listen<'a>( &mut self, ) -> Result> + 'a> { let listener = self.0.events_listen(()).await?.into_inner(); Ok(listener.map(|item| { let event = item?.event.ok_or(Error::MissingDaemonEvent)?; DaemonEvent::try_from(event) })) } /// DEPRECATED: Prefer to use `prepare_restart_v2`. pub async fn prepare_restart(&mut self) -> Result<()> { self.0.prepare_restart(()).await?; Ok(()) } /// Tell the daemon to get ready for a restart by securing a user, i.e. putting firewall rules /// in place. /// /// - `shutdown`: Whether the daemon should shutdown immediately after its prepare-for-restart /// routine. pub async fn prepare_restart_v2(&mut self, shutdown: bool) -> Result<()> { self.0.prepare_restart_v2(shutdown).await?; Ok(()) } pub async fn factory_reset(&mut self) -> Result<()> { self.0.factory_reset(()).await?; Ok(()) } pub async fn get_current_version(&mut self) -> Result { Ok(self.0.get_current_version(()).await?.into_inner()) } pub async fn get_version_info(&mut self) -> Result { let version_info = self.0.get_version_info(()).await?.into_inner(); AppVersionInfo::try_from(version_info).map_err(Error::InvalidResponse) } pub async fn get_relay_locations(&mut self) -> Result { let list = self.0.get_relay_locations(()).await?.into_inner(); mullvad_types::relay_list::RelayList::try_from(list).map_err(Error::InvalidResponse) } pub async fn get_bridges(&mut self) -> Result { let list = self.0.get_bridges(()).await?.into_inner(); mullvad_types::relay_list::BridgeList::try_from(list).map_err(Error::InvalidResponse) } pub async fn get_api_access_methods(&mut self) -> Result> { let access_method_settings = self .0 .get_settings(()) .await? .into_inner() .api_access_methods .ok_or(Error::ApiAccessMethodSettingsNotFound) .and_then(|access_method_settings| { access_method::Settings::try_from(access_method_settings) .map_err(Error::InvalidResponse) })?; Ok(access_method_settings.iter().cloned().collect()) } pub async fn get_api_access_method( &mut self, id: &access_method::Id, ) -> Result { self.get_api_access_methods() .await? .into_iter() .find(|api_access_method| api_access_method.get_id() == *id) .ok_or(Error::ApiAccessMethodNotFound) } pub async fn get_current_api_access_method(&mut self) -> Result { self.0 .get_current_api_access_method(()) .await .map_err(Error::from) .map(tonic::Response::into_inner) .and_then(|access_method| { AccessMethodSetting::try_from(access_method).map_err(Error::InvalidResponse) }) } pub async fn test_api_access_method(&mut self, id: access_method::Id) -> Result { let result = self .0 .test_api_access_method_by_id(types::Uuid::from(id)) .await?; Ok(result.into_inner()) } pub async fn test_custom_api_access_method( &mut self, config: talpid_types::net::proxy::CustomProxy, ) -> Result { let result = self .0 .test_custom_api_access_method(types::CustomProxy::from(config)) .await?; Ok(result.into_inner()) } pub async fn update_relay_locations(&mut self) -> Result<()> { self.0.update_relay_locations(()).await?; Ok(()) } pub async fn set_relay_settings(&mut self, update: RelaySettings) -> Result<()> { let update = types::RelaySettings::from(update); self.0.set_relay_settings(update).await?; Ok(()) } pub async fn set_obfuscation_settings(&mut self, settings: ObfuscationSettings) -> Result<()> { let settings = types::ObfuscationSettings::from(&settings); self.0.set_obfuscation_settings(settings).await?; Ok(()) } pub async fn get_settings(&mut self) -> Result { let settings = self.0.get_settings(()).await?.into_inner(); Settings::try_from(settings).map_err(Error::InvalidResponse) } pub async fn reset_settings(&mut self) -> Result<()> { self.0.reset_settings(()).await?; Ok(()) } pub async fn set_allow_lan(&mut self, state: bool) -> Result<()> { self.0.set_allow_lan(state).await?; Ok(()) } pub async fn set_show_beta_releases(&mut self, state: bool) -> Result<()> { self.0.set_show_beta_releases(state).await?; Ok(()) } pub async fn set_lockdown_mode(&mut self, state: bool) -> Result<()> { self.0.set_lockdown_mode(state).await?; Ok(()) } pub async fn set_auto_connect(&mut self, state: bool) -> Result<()> { self.0.set_auto_connect(state).await?; Ok(()) } pub async fn set_wireguard_mtu(&mut self, mtu: Option) -> Result<()> { self.0 .set_wireguard_mtu(mtu.map(u32::from).unwrap_or(0)) .await?; Ok(()) } pub async fn set_enable_ipv6(&mut self, state: bool) -> Result<()> { self.0.set_enable_ipv6(state).await?; Ok(()) } pub async fn set_userspace_wireguard(&mut self, state: bool) -> Result<()> { self.0.set_userspace_wireguard(state).await?; Ok(()) } pub async fn set_quantum_resistant_tunnel( &mut self, state: QuantumResistantState, ) -> Result<()> { let state = types::QuantumResistantState::from(state); self.0.set_quantum_resistant_tunnel(state).await?; Ok(()) } #[cfg(daita)] pub async fn set_enable_daita(&mut self, value: bool) -> Result<()> { self.0.set_enable_daita(value).await?; Ok(()) } #[cfg(daita)] pub async fn set_daita_direct_only(&mut self, value: bool) -> Result<()> { self.0.set_daita_direct_only(value).await?; Ok(()) } #[cfg(daita)] pub async fn set_daita_settings(&mut self, settings: DaitaSettings) -> Result<()> { let settings = types::DaitaSettings::from(settings); self.0.set_daita_settings(settings).await?; Ok(()) } pub async fn set_dns_options(&mut self, options: DnsOptions) -> Result<()> { let options = types::DnsOptions::from(&options); self.0.set_dns_options(options).await?; Ok(()) } pub async fn set_relay_override(&mut self, relay_override: RelayOverride) -> Result<()> { let r#override = types::RelayOverride::from(relay_override); self.0.set_relay_override(r#override).await?; Ok(()) } pub async fn clear_all_relay_overrides(&mut self) -> Result<()> { self.0.clear_all_relay_overrides(()).await?; Ok(()) } pub async fn create_new_account(&mut self) -> Result { Ok(self .0 .create_new_account(()) .await .map_err(map_device_error)? .into_inner()) } pub async fn login_account(&mut self, account: AccountNumber) -> Result<()> { self.0 .login_account(account) .await .map_err(map_device_error)?; Ok(()) } pub async fn logout_account(&mut self, source: &str) -> Result<()> { self.0.logout_account(source.to_owned()).await?; Ok(()) } pub async fn get_account_data(&mut self, account: AccountNumber) -> Result { let data = self.0.get_account_data(account).await?.into_inner(); AccountData::try_from(data).map_err(Error::InvalidResponse) } pub async fn get_account_history(&mut self) -> Result> { let history = self.0.get_account_history(()).await?.into_inner(); Ok(history.number) } pub async fn clear_account_history(&mut self) -> Result<()> { self.0.clear_account_history(()).await?; Ok(()) } // get_www_auth_token pub async fn submit_voucher(&mut self, voucher: String) -> Result { let result = self .0 .submit_voucher(voucher) .await .map_err(|error| match error.code() { Code::NotFound => Error::InvalidVoucher, Code::ResourceExhausted => Error::UsedVoucher, _other => Error::Rpc(Box::new(error)), })? .into_inner(); VoucherSubmission::try_from(result).map_err(Error::InvalidResponse) } pub async fn get_device(&mut self) -> Result { let state = self .0 .get_device(()) .await .map_err(map_device_error)? .into_inner(); DeviceState::try_from(state).map_err(Error::InvalidResponse) } pub async fn update_device(&mut self) -> Result<()> { self.0.update_device(()).await.map_err(map_device_error)?; Ok(()) } pub async fn list_devices(&mut self, account: AccountNumber) -> Result> { let list = self .0 .list_devices(account) .await .map_err(map_device_error)? .into_inner(); list.devices .into_iter() .map(|d| Device::try_from(d).map_err(Error::InvalidResponse)) .collect::>() } pub async fn remove_device( &mut self, account: AccountNumber, device_id: DeviceId, ) -> Result<()> { self.0 .remove_device(types::DeviceRemoval { account_number: account, device_id, }) .await .map_err(map_device_error)?; Ok(()) } pub async fn set_wireguard_rotation_interval( &mut self, interval: RotationInterval, ) -> Result<()> { let duration = types::Duration::try_from(*interval.as_duration()) .map_err(|_| Error::DurationTooLarge)?; self.0.set_wireguard_rotation_interval(duration).await?; Ok(()) } pub async fn reset_wireguard_rotation_interval(&mut self) -> Result<()> { self.0.reset_wireguard_rotation_interval(()).await?; Ok(()) } pub async fn rotate_wireguard_key(&mut self) -> Result<()> { self.0.rotate_wireguard_key(()).await?; Ok(()) } pub async fn get_wireguard_key(&mut self) -> Result { let key = self.0.get_wireguard_key(()).await?.into_inner(); PublicKey::try_from(key).map_err(Error::InvalidResponse) } pub async fn create_custom_list(&mut self, name: String) -> Result { let request = types::NewCustomList { name, locations: Vec::new(), }; let id = self .0 .create_custom_list(request) .await .map_err(map_custom_list_error)? .into_inner(); Id::from_str(&id).map_err(|_| Error::CustomListListNotFound) } pub async fn delete_custom_list(&mut self, id: Id) -> Result<()> { self.0 .delete_custom_list(id.to_string()) .await .map_err(map_custom_list_error)?; Ok(()) } pub async fn update_custom_list(&mut self, custom_list: CustomList) -> Result<()> { self.0 .update_custom_list(types::CustomList::from(custom_list)) .await .map_err(map_custom_list_error)?; Ok(()) } /// Remove all custom lists. pub async fn clear_custom_lists(&mut self) -> Result<()> { self.0 .clear_custom_lists(()) .await .map_err(map_custom_list_error)?; Ok(()) } pub async fn add_access_method( &mut self, name: String, enabled: bool, access_method: AccessMethod, ) -> Result<()> { let request = types::NewAccessMethodSetting { name, enabled, access_method: Some(types::AccessMethod::from(access_method)), }; self.0 .add_api_access_method(request) .await .map_err(map_api_access_method_error)?; Ok(()) } pub async fn remove_access_method( &mut self, api_access_method: access_method::Id, ) -> Result<()> { self.0 .remove_api_access_method(types::Uuid::from(api_access_method)) .await?; Ok(()) } pub async fn update_access_method( &mut self, access_method_update: AccessMethodSetting, ) -> Result<()> { self.0 .update_api_access_method(types::AccessMethodSetting::from(access_method_update)) .await .map_err(map_api_access_method_error)?; Ok(()) } /// Remove all custom API access methods. pub async fn clear_custom_access_methods(&mut self) -> Result<()> { self.0.clear_custom_api_access_methods(()).await?; Ok(()) } /// Set the [`AccessMethod`] which `AccessModeSelector` should pick. pub async fn set_access_method(&mut self, api_access_method: access_method::Id) -> Result<()> { self.0 .set_api_access_method(types::Uuid::from(api_access_method)) .await?; Ok(()) } pub async fn get_split_tunnel_processes(&mut self) -> Result> { use futures::TryStreamExt; let procs = self.0.get_split_tunnel_processes(()).await?.into_inner(); procs.try_collect().await.map_err(Error::from) } pub async fn add_split_tunnel_process(&mut self, pid: i32) -> Result<()> { self.0.add_split_tunnel_process(pid).await?; Ok(()) } pub async fn remove_split_tunnel_process(&mut self, pid: i32) -> Result<()> { self.0.remove_split_tunnel_process(pid).await?; Ok(()) } pub async fn clear_split_tunnel_processes(&mut self) -> Result<()> { self.0.clear_split_tunnel_processes(()).await?; Ok(()) } pub async fn add_split_tunnel_app>(&mut self, path: P) -> Result<()> { let path = path.as_ref().to_str().ok_or(Error::PathMustBeUtf8)?; self.0.add_split_tunnel_app(path.to_owned()).await?; Ok(()) } pub async fn remove_split_tunnel_app>(&mut self, path: P) -> Result<()> { let path = path.as_ref().to_str().ok_or(Error::PathMustBeUtf8)?; self.0.remove_split_tunnel_app(path.to_owned()).await?; Ok(()) } pub async fn clear_split_tunnel_apps(&mut self) -> Result<()> { self.0.clear_split_tunnel_apps(()).await?; Ok(()) } /// Toggle split tunneling on (`state: true`) or off (`state: false`). pub async fn set_split_tunnel_state(&mut self, state: bool) -> Result<()> { self.0.set_split_tunnel_state(state).await?; Ok(()) } #[cfg(target_os = "windows")] pub async fn get_excluded_processes(&mut self) -> Result> { let procs = self.0.get_excluded_processes(()).await?.into_inner(); Ok(procs .processes .into_iter() .map(ExcludedProcess::from) .collect::>()) } // check_volumes pub async fn apply_json_settings(&mut self, blob: String) -> Result<()> { self.0.apply_json_settings(blob).await?; Ok(()) } pub async fn export_json_settings(&mut self) -> Result { let blob = self.0.export_json_settings(()).await?; Ok(blob.into_inner()) } pub async fn get_feature_indicators(&mut self) -> Result { Ok(FeatureIndicators::from( self.0.get_feature_indicators(()).await?.into_inner(), )) } // Debug features pub async fn disable_relay(&mut self, relay: String) -> Result<()> { self.0.disable_relay(relay).await?; Ok(()) } pub async fn enable_relay(&mut self, relay: String) -> Result<()> { self.0.enable_relay(relay).await?; Ok(()) } pub async fn get_rollout_threshold(&mut self) -> Result { let rollout = self.0.get_rollout_threshold(()).await?; let threshold = rollout.into_inner().threshold; Ok(threshold) } pub async fn generate_new_rollout_threshold(&mut self) -> Result { let rollout = self.0.regenerate_rollout_threshold(()).await?; let threshold = rollout.into_inner().threshold; Ok(threshold) } pub async fn set_new_rollout_threshold_seed(&mut self, seed: u32) -> Result<()> { self.0 .set_rollout_threshold_seed(types::Seed { seed }) .await?; Ok(()) } pub async fn set_wireguard_allowed_ips(&mut self, allowed_ips: AllowedIps) -> Result<()> { self.0 .set_wireguard_allowed_ips(types::AllowedIpsList { values: allowed_ips.0.iter().map(ToString::to_string).collect(), }) .await?; Ok(()) } pub async fn set_log_filter(&mut self, level: String) -> Result<()> { self.0.set_log_filter(types::LogFilter::from(level)).await?; Ok(()) } pub async fn log_listen(&mut self) -> Result>> { let listener = self.0.log_listen(()).await?.into_inner(); Ok(listener.map(|item| Ok(item?.message))) } #[cfg(feature = "personal-vpn")] pub async fn set_personal_vpn_config( &mut self, config: Option, ) -> Result { let proto_config = match config { Some(c) => types::PersonalVpnConfig::from(c), None => types::PersonalVpnConfig { tunnel: None, peer: None, }, }; let response = self.0.set_personal_vpn_config(proto_config).await?; Ok(response.into_inner().error) } #[cfg(feature = "personal-vpn")] pub async fn set_personal_vpn_config_status(&mut self, enabled: bool) -> Result<()> { self.0.set_personal_vpn_config_status(enabled).await?; Ok(()) } /// Import a personal VPN configuration from the contents of a wg-quick file. #[cfg(feature = "personal-vpn")] pub async fn import_personal_vpn_config(&mut self, body: String) -> Result { let response = self.0.import_personal_vpn_config(body).await?; Ok(response.into_inner().error) } } #[cfg(not(target_os = "android"))] fn map_device_error(status: Status) -> Error { match status.code() { Code::ResourceExhausted => Error::TooManyDevices, Code::Unauthenticated => Error::InvalidAccount, Code::AlreadyExists => Error::AlreadyLoggedIn, Code::NotFound => Error::DeviceNotFound, _other => Error::Rpc(Box::new(status)), } } #[cfg(not(target_os = "android"))] fn map_custom_list_error(status: Status) -> Error { match (status.code(), status.details()) { (Code::NotFound, crate::CUSTOM_LIST_LIST_NOT_FOUND_DETAILS) => { Error::CustomListListNotFound } (Code::AlreadyExists, crate::CUSTOM_LIST_LIST_EXISTS_DETAILS) => Error::CustomListExists, _other => Error::Rpc(Box::new(status)), } } #[cfg(not(target_os = "android"))] fn map_api_access_method_error(status: Status) -> Error { match (status.code(), status.details()) { (Code::AlreadyExists, crate::API_ACCESS_METHOD_EXISTS_DETAILS) => { Error::ApiAccessMethodExists } _other => Error::Rpc(Box::new(status)), } } // Types that are only defined in the protobuf interface (as opposed to *-types crates). /// Details about how a leak happened. #[derive(Debug)] pub struct LeakInfo { /// On what interface the leaky traffic was detected. pub interface: String, /// What network nodes that was reached. pub reachable_nodes: Vec, } impl TryFrom for LeakInfo { type Error = Error; fn try_from(leak: types::LeakInfo) -> Result { let reachable_nodes = leak .ip_addrs .into_iter() .map(|ip| ip.parse().map_err(Error::IpAddr)) .collect::>()?; Ok(LeakInfo { interface: leak.interface, reachable_nodes, }) } } #[cfg(not(target_os = "android"))] pub struct RelaySelectorClient(crate::RelaySelectorServiceClient); #[cfg(not(target_os = "android"))] impl RelaySelectorClient { pub async fn new() -> Result { let channel = crate::grpc_transport_channel().await?; let client = crate::RelaySelectorServiceClient::new(channel); Ok(Self(client)) } pub async fn partition_relays( &mut self, predicate: crate::types::relay_selector::Predicate, ) -> Result { let result = self.0.partition_relays(predicate).await?.into_inner(); Ok(result) } }