diff --git a/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpoint.kt b/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpoint.kt index 7325e3f61b1f..4b5beacf491b 100644 --- a/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpoint.kt +++ b/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpoint.kt @@ -8,6 +8,5 @@ import kotlinx.parcelize.Parcelize data class ApiEndpoint( val address: InetSocketAddress, val disableAddressCache: Boolean, - val disableTls: Boolean, - val forceDirectConnection: Boolean + val disableTls: Boolean ) : Parcelable diff --git a/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/CustomApiEndpointConfiguration.kt b/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/CustomApiEndpointConfiguration.kt index 5790f8c73f06..b1559be777ef 100644 --- a/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/CustomApiEndpointConfiguration.kt +++ b/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/CustomApiEndpointConfiguration.kt @@ -10,14 +10,12 @@ data class CustomApiEndpointConfiguration( val hostname: String, val port: Int, val disableAddressCache: Boolean = true, - val disableTls: Boolean = false, - val forceDirectConnection: Boolean = true + val disableTls: Boolean = false ) : ApiEndpointConfiguration { override fun apiEndpoint() = ApiEndpoint( address = InetSocketAddress(hostname, port), disableAddressCache = disableAddressCache, - disableTls = disableTls, - forceDirectConnection = forceDirectConnection + disableTls = disableTls ) } diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt index f699b3cadc5b..702aa72db46b 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt @@ -59,8 +59,7 @@ abstract class MockApiTest { InetAddress.getLocalHost().hostName, port, disableAddressCache = true, - disableTls = true, - forceDirectConnection = true + disableTls = true ) } } diff --git a/mullvad-api/src/access.rs b/mullvad-api/src/access.rs index 276cc1f5613e..569c01f085a7 100644 --- a/mullvad-api/src/access.rs +++ b/mullvad-api/src/access.rs @@ -38,7 +38,7 @@ struct AccountState { impl AccessTokenStore { pub(crate) fn new(service: RequestServiceHandle) -> Self { - let factory = rest::RequestFactory::new(&API.host, None); + let factory = rest::RequestFactory::new(API.host(), None); let (tx, rx) = mpsc::unbounded(); tokio::spawn(Self::service_requests(rx, service, factory)); Self { tx } diff --git a/mullvad-api/src/address_cache.rs b/mullvad-api/src/address_cache.rs index e4bcf9bde7ed..ea93d96e26a9 100644 --- a/mullvad-api/src/address_cache.rs +++ b/mullvad-api/src/address_cache.rs @@ -31,7 +31,7 @@ pub struct AddressCache { impl AddressCache { /// Initialize cache using the hardcoded address, and write changes to `write_path`. pub fn new(write_path: Option>) -> Result { - Self::new_inner(API.addr, write_path) + Self::new_inner(API.address(), write_path) } /// Initialize cache using `read_path`, and write changes to `write_path`. @@ -53,7 +53,7 @@ impl AddressCache { /// Returns the address if the hostname equals `API.host`. Otherwise, returns `None`. pub async fn resolve_hostname(&self, hostname: &str) -> Option { - if hostname.eq_ignore_ascii_case(&API.host) { + if hostname.eq_ignore_ascii_case(API.host()) { Some(self.get_address().await) } else { None diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index 237ed100d416..adba2367643c 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -103,95 +103,169 @@ impl Deref for LazyManual { /// A hostname and socketaddr to reach the Mullvad REST API over. #[derive(Debug)] pub struct ApiEndpoint { - pub host: String, - pub addr: SocketAddr, + /// An overriden API hostname. Initialized with the value of the environment + /// variable `MULLVAD_API_HOST` if it has been set. + /// + /// Use the associated function [`Self::host`] to read this value with a + /// default fallback if `MULLVAD_API_HOST` was not set. + pub host: Option, + /// An overriden API address. Initialized with the value of the environment + /// variable `MULLVAD_API_ADDR` if it has been set. + /// + /// Use the associated function [`Self::address()`] to read this value with + /// a default fallback if `MULLVAD_API_ADDR` was not set. + /// + /// # Note + /// + /// If [`Self::address`] is populated with [`Some(SocketAddr)`], it should + /// always be respected when establishing API connections. + pub address: Option, #[cfg(feature = "api-override")] pub disable_address_cache: bool, #[cfg(feature = "api-override")] pub disable_tls: bool, - #[cfg(feature = "api-override")] - pub force_direct_connection: bool, } impl ApiEndpoint { + const API_HOST_DEFAULT: &'static str = "api.mullvad.net"; + const API_IP_DEFAULT: IpAddr = IpAddr::V4(Ipv4Addr::new(45, 83, 223, 196)); + const API_PORT_DEFAULT: u16 = 443; + + const API_HOST_VAR: &'static str = "MULLVAD_API_HOST"; + const API_ADDR_VAR: &'static str = "MULLVAD_API_ADDR"; + const DISABLE_TLS_VAR: &'static str = "MULLVAD_API_DISABLE_TLS"; + /// Returns the endpoint to connect to the API over. /// /// # Panics /// - /// Panics if `MULLVAD_API_ADDR` has invalid contents or if only one of - /// `MULLVAD_API_ADDR` or `MULLVAD_API_HOST` has been set but not the other. + /// Panics if `MULLVAD_API_ADDR`, `MULLVAD_API_HOST` or + /// `MULLVAD_API_DISABLE_TLS` has invalid contents. + #[cfg(feature = "api-override")] pub fn from_env_vars() -> ApiEndpoint { - const API_HOST_DEFAULT: &str = "api.mullvad.net"; - const API_IP_DEFAULT: IpAddr = IpAddr::V4(Ipv4Addr::new(45, 83, 223, 196)); - const API_PORT_DEFAULT: u16 = 443; - - fn read_var(key: &'static str) -> Option { - use std::env; - match env::var(key) { - Ok(v) => Some(v), - Err(env::VarError::NotPresent) => None, - Err(env::VarError::NotUnicode(_)) => panic!("{key} does not contain valid UTF-8"), - } - } - - let host_var = read_var("MULLVAD_API_HOST"); - let address_var = read_var("MULLVAD_API_ADDR"); - let disable_tls_var = read_var("MULLVAD_API_DISABLE_TLS"); + let host_var = Self::read_var(ApiEndpoint::API_HOST_VAR); + let address_var = Self::read_var(ApiEndpoint::API_ADDR_VAR); + let disable_tls_var = Self::read_var(ApiEndpoint::DISABLE_TLS_VAR); - #[cfg_attr(not(feature = "api-override"), allow(unused_mut))] let mut api = ApiEndpoint { - host: API_HOST_DEFAULT.to_owned(), - addr: SocketAddr::new(API_IP_DEFAULT, API_PORT_DEFAULT), - #[cfg(feature = "api-override")] - disable_address_cache: false, - #[cfg(feature = "api-override")] + host: host_var.clone(), + address: None, + disable_address_cache: true, disable_tls: false, - #[cfg(feature = "api-override")] - force_direct_connection: false, }; - #[cfg(feature = "api-override")] - { - use std::net::ToSocketAddrs; - - if host_var.is_none() && address_var.is_none() { - if disable_tls_var.is_some() { - log::warn!("MULLVAD_API_DISABLE_TLS is ignored since MULLVAD_API_HOST and MULLVAD_API_ADDR are not set"); - } - return api; - } - - let scheme = if let Some(disable_tls_var) = disable_tls_var { - api.disable_tls = disable_tls_var != "0"; - "http://" - } else { - "https://" - }; - - if let Some(user_host) = host_var { - api.host = user_host; + api.address = match address_var { + Some(user_addr) => { + let addr = user_addr.parse().unwrap_or_else(|_| { + panic!( + "{api_addr}={user_addr} is not a valid socketaddr", + api_addr = ApiEndpoint::API_ADDR_VAR, + ) + }); + Some(addr) } - if let Some(user_addr) = address_var { - api.addr = user_addr - .parse() - .expect("MULLVAD_API_ADDR is not a valid socketaddr"); - } else { - log::warn!("Resolving API IP from MULLVAD_API_HOST"); - api.addr = format!("{}:{}", api.host, API_PORT_DEFAULT) + None => { + use std::net::ToSocketAddrs; + log::debug!( + "{api_addr} not found. Resolving API IP from {api_host}", + api_addr = ApiEndpoint::API_ADDR_VAR, + api_host = ApiEndpoint::API_HOST_VAR + ); + format!("{}:{}", api.host(), ApiEndpoint::API_PORT_DEFAULT) .to_socket_addrs() .expect("failed to resolve API host") .next() - .expect("API host yielded 0 addresses"); } - api.disable_address_cache = true; - api.force_direct_connection = true; - log::debug!("Overriding API. Using {} at {scheme}{}", api.host, api.addr); + }; + + if api.host.is_none() && api.address.is_none() { + if disable_tls_var.is_some() { + log::warn!( + "{disable_tls} is ignored since {api_host} and {api_addr} are not set", + disable_tls = ApiEndpoint::DISABLE_TLS_VAR, + api_host = ApiEndpoint::API_HOST_VAR, + api_addr = ApiEndpoint::API_ADDR_VAR, + ); + } + } else { + api.disable_tls = disable_tls_var + .as_ref() + .map(|disable_tls| disable_tls != "0") + .unwrap_or(api.disable_tls); + + log::debug!( + "Overriding API. Using {host} at {scheme}{addr}", + host = api.host(), + addr = api.address(), + scheme = if api.disable_tls { + "http://" + } else { + "https://" + } + ); } - #[cfg(not(feature = "api-override"))] + api + } + + /// Returns the endpoint to connect to the API over. + /// + /// # Panics + /// + /// Panics if `MULLVAD_API_ADDR`, `MULLVAD_API_HOST` or + /// `MULLVAD_API_DISABLE_TLS` has invalid contents. + #[cfg(not(feature = "api-override"))] + pub fn from_env_vars() -> ApiEndpoint { + let host_var = Self::read_var(ApiEndpoint::API_HOST_VAR); + let address_var = Self::read_var(ApiEndpoint::API_ADDR_VAR); + let disable_tls_var = Self::read_var(ApiEndpoint::DISABLE_TLS_VAR); + if host_var.is_some() || address_var.is_some() || disable_tls_var.is_some() { - log::warn!("These variables are ignored in production builds: MULLVAD_API_HOST, MULLVAD_API_ADDR, MULLVAD_API_DISABLE_TLS"); + log::warn!( + "These variables are ignored in production builds: {api_host}, {api_addr}, {disable_tls}", + api_host = ApiEndpoint::API_HOST_VAR, + api_addr = ApiEndpoint::API_ADDR_VAR, + disable_tls = ApiEndpoint::DISABLE_TLS_VAR + ); + } + + ApiEndpoint { + host: None, + address: None, + } + } + + /// Read the [`Self::host`] value, falling back to + /// [`Self::API_HOST_DEFAULT`] as default value if it does not exist. + pub fn host(&self) -> &str { + self.host + .as_deref() + .unwrap_or(ApiEndpoint::API_HOST_DEFAULT) + } + + /// Read the [`Self::address`] value, falling back to + /// [`Self::API_IP_DEFAULT`]:[`Self::API_PORT_DEFAULT`] as default if it + /// does not exist. + pub fn address(&self) -> SocketAddr { + self.address.unwrap_or(SocketAddr::new( + ApiEndpoint::API_IP_DEFAULT, + ApiEndpoint::API_PORT_DEFAULT, + )) + } + + /// Try to read the value of an environment variable. Returns `None` if the + /// environment variable has not been set. + /// + /// # Panics + /// + /// Panics if the environment variable was found, but it did not contain + /// valid unicode data. + fn read_var(key: &'static str) -> Option { + use std::env; + match env::var(key) { + Ok(v) => Some(v), + Err(env::VarError::NotPresent) => None, + Err(env::VarError::NotUnicode(_)) => panic!("{key} does not contain valid UTF-8"), } - api } } @@ -314,14 +388,14 @@ impl Runtime { ) -> rest::MullvadRestHandle { let service = self .new_request_service( - Some(API.host.clone()), + Some(API.host().to_string()), proxy_provider, #[cfg(target_os = "android")] self.socket_bypass_tx.clone(), ) .await; let token_store = access::AccessTokenStore::new(service.clone()); - let factory = rest::RequestFactory::new(&API.host, Some(token_store)); + let factory = rest::RequestFactory::new(API.host(), Some(token_store)); rest::MullvadRestHandle::new( service, diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs index 9f1e88a751a5..d5b82052e522 100644 --- a/mullvad-api/src/rest.rs +++ b/mullvad-api/src/rest.rs @@ -26,9 +26,6 @@ use std::{ }; use talpid_types::ErrorExt; -#[cfg(feature = "api-override")] -use crate::API; - pub use hyper::StatusCode; const USER_AGENT: &str = "mullvad-app"; @@ -147,14 +144,7 @@ impl + Unpin + Send + 'static> RequestServic socket_bypass_tx.clone(), ); - #[cfg(feature = "api-override")] - let force_direct_connection = API.force_direct_connection; - #[cfg(not(feature = "api-override"))] - let force_direct_connection = false; - - if force_direct_connection { - log::debug!("API proxies are disabled"); - } else if let Some(config) = proxy_config_provider.next().await { + if let Some(config) = proxy_config_provider.next().await { connector_handle.set_connection_mode(config); } @@ -185,17 +175,9 @@ impl + Unpin + Send + 'static> RequestServic self.connector_handle.reset(); } RequestCommand::NextApiConfig(completion_tx) => { - #[cfg(feature = "api-override")] - let force_direct_connection = API.force_direct_connection; - #[cfg(not(feature = "api-override"))] - let force_direct_connection = false; - - if force_direct_connection { - log::debug!("Ignoring API connection mode"); - } else if let Some(connection_mode) = self.proxy_config_provider.next().await { + if let Some(connection_mode) = self.proxy_config_provider.next().await { self.connector_handle.set_connection_mode(connection_mode); } - let _ = completion_tx.send(Ok(())); } } @@ -632,8 +614,10 @@ impl MullvadRestHandle { availability, }; #[cfg(feature = "api-override")] - if API.disable_address_cache { - return handle; + { + if crate::API.disable_address_cache { + return handle; + } } handle.spawn_api_address_fetcher(address_cache); handle diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 3f6f1747a616..caf39ec87430 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -32,65 +32,39 @@ pub enum Message { Resolve(ResponseTx, AccessMethodSetting), } -/// A [`NewAccessMethodEvent`] is emitted when the active access method changes, -/// which happens in any of the following two scenarios: -/// -/// * When a [`mullvad_api::rest::RequestService`] requests a new -/// [`ApiConnectionMode`] from the running [`AccessModeSelector`]. This will -/// lead to a [`crate::InternalDaemonEvent::AccessMethodEvent`] being sent to -/// the daemon, which in turn will notify all clients about the new access -/// method. -/// -/// * When testing if some [`AccessMethodSetting`] can be used to reach the -/// Mullvad API. In this scenario, the currently active access method will -/// temporarily change (approximately for the duration of 1 API call). Since -/// this is just an internal test which should be opaque to any client, it -/// should not produce any unwanted noise and as such it is *not* broadcasted -/// after the daemon is done processing this [`NewAccessMethodEvent`]. -pub struct NewAccessMethodEvent { - /// The new active [`AccessMethodSetting`]. - pub setting: AccessMethodSetting, - /// The endpoint which represents how to connect to the Mullvad API and - /// which clients are allowed to initiate such a connection. - pub endpoint: AllowedEndpoint, - /// If the daemon should notify clients about the new access method. +/// Calling [`AccessMethodEvent::send`] will cause a +/// [`crate::InternalDaemonEvent::AccessMethodEvent`] being sent to the daemon, +/// which in turn will handle updating the firewall and notifying clients as +/// applicable. +pub enum AccessMethodEvent { + /// A [`AccessMethodEvent::New`] event is emitted when the active access + /// method changes. /// - /// Defaults to `true`. - pub announce: bool, -} - -impl NewAccessMethodEvent { - /// Create a new [`NewAccessMethodEvent`] for the daemon to process. A - /// [`oneshot::Receiver`] can be used to await the daemon while it finishes - /// handling the new event. - pub fn new(setting: AccessMethodSetting, endpoint: AllowedEndpoint) -> NewAccessMethodEvent { - NewAccessMethodEvent { - setting, - endpoint, - announce: true, - } - } - - /// Whether the daemon should notify clients about the new access method or - /// not. + /// This happens when a [`mullvad_api::rest::RequestService`] requests a new + /// [`ApiConnectionMode`] from the running [`AccessModeSelector`]. + New { + /// The new active [`AccessMethodSetting`]. + setting: AccessMethodSetting, + /// The endpoint which represents how to connect to the Mullvad API and + /// which clients are allowed to initiate such a connection. + endpoint: AllowedEndpoint, + }, + /// Emitted when the the firewall should be updated. /// - /// * If `announce` is set to `true` the daemon will broadcast this event to - /// clients. - /// * If `announce` is set to `false` the daemon will *not* broadcast this - /// event. - pub fn announce(mut self, announce: bool) -> Self { - self.announce = announce; - self - } + /// This is useful for example when testing if some [`AccessMethodSetting`] + /// can be used to reach the Mullvad API. In this scenario, the currently + /// active access method will temporarily change (approximately for the + /// duration of 1 API call). Since this is just an internal test which + /// should be opaque to any client, it should not produce any unwanted noise + /// and as such it is *not* broadcasted after the daemon is done processing + /// this [`AccessMethodEvent::Allow`]. + Allow { endpoint: AllowedEndpoint }, +} - /// Send an internal daemon event which will punch a hole in the firewall - /// for the connection mode we are testing. - /// - /// Returns the channel on which the daemon will send a message over when it - /// is done applying the firewall changes. +impl AccessMethodEvent { pub(crate) async fn send( self, - daemon_event_sender: DaemonEventSender<(NewAccessMethodEvent, oneshot::Sender<()>)>, + daemon_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>, ) -> std::result::Result<(), Canceled> { // It is up to the daemon to actually allow traffic to/from `api_endpoint` // by updating the firewall. This [`oneshot::Sender`] allows the daemon to @@ -251,7 +225,7 @@ pub struct AccessModeSelector { relay_selector: RelaySelector, connection_modes: ConnectionModesIterator, address_cache: AddressCache, - access_method_event_sender: DaemonEventSender<(NewAccessMethodEvent, oneshot::Sender<()>)>, + access_method_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>, current: ResolvedConnectionMode, } @@ -260,7 +234,7 @@ impl AccessModeSelector { cache_dir: PathBuf, relay_selector: RelaySelector, connection_modes: Vec, - access_method_event_sender: DaemonEventSender<(NewAccessMethodEvent, oneshot::Sender<()>)>, + access_method_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>, address_cache: AddressCache, ) -> Result { let (cmd_tx, cmd_rx) = mpsc::unbounded(); @@ -348,6 +322,36 @@ impl AccessModeSelector { } async fn next_connection_mode(&mut self) -> Result { + #[cfg(feature = "api-override")] + { + use mullvad_api::API; + // If the API address has been explicitly overridden, it should + // always be used. This implies that a direct API connection mode is + // used. + if API.address.is_some() { + log::debug!("API proxies are disabled"); + let endpoint = resolve_allowed_endpoint( + &ApiConnectionMode::Direct, + // Note that the address cache *should* be initialized with + // the overridden API endpoint, so we can simply fetch the + // endpoint address from it. + self.address_cache.get_address().await, + ); + let daemon_sender = self.access_method_event_sender.clone(); + tokio::spawn(async move { + let _ = AccessMethodEvent::Allow { endpoint } + .send(daemon_sender) + .await; + }); + return Ok(ApiConnectionMode::Direct); + } + + log::debug!( + "The `api-override` feature is enabled, but the API address \ + was not overridden. Selecting API access methods as normal" + ); + } + let access_method = self.connection_modes.next().ok_or(Error::NoAccessMethods)?; log::info!( "A new API access method has been selected: {name}", @@ -365,7 +369,7 @@ impl AccessModeSelector { let endpoint = resolved.endpoint.clone(); let daemon_sender = self.access_method_event_sender.clone(); tokio::spawn(async move { - let _ = NewAccessMethodEvent::new(setting, endpoint) + let _ = AccessMethodEvent::New { setting, endpoint } .send(daemon_sender) .await; }); diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 06e289d22d30..bb257ebc60a6 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -27,7 +27,7 @@ pub mod version; mod version_check; use crate::target_state::PersistentTargetState; -use api::NewAccessMethodEvent; +use api::AccessMethodEvent; use device::{AccountEvent, PrivateAccountAndDevice, PrivateDeviceEvent}; use futures::{ channel::{mpsc, oneshot}, @@ -374,7 +374,7 @@ pub(crate) enum InternalDaemonEvent { DeviceEvent(AccountEvent), /// Sent when access methods are changed in any way (new active access method). AccessMethodEvent { - event: NewAccessMethodEvent, + event: AccessMethodEvent, endpoint_active_tx: oneshot::Sender<()>, }, /// Handles updates from versions without devices. @@ -416,8 +416,8 @@ impl From for InternalDaemonEvent { } } -impl From<(NewAccessMethodEvent, oneshot::Sender<()>)> for InternalDaemonEvent { - fn from(event: (NewAccessMethodEvent, oneshot::Sender<()>)) -> Self { +impl From<(AccessMethodEvent, oneshot::Sender<()>)> for InternalDaemonEvent { + fn from(event: (AccessMethodEvent, oneshot::Sender<()>)) -> Self { InternalDaemonEvent::AccessMethodEvent { event: event.0, endpoint_active_tx: event.1, @@ -1341,28 +1341,39 @@ where fn handle_access_method_event( &mut self, - event: NewAccessMethodEvent, + event: AccessMethodEvent, endpoint_active_tx: oneshot::Sender<()>, ) { - // Update the firewall to exempt a new API endpoint. - let (completion_tx, completion_rx) = oneshot::channel(); - self.send_tunnel_command(TunnelCommand::AllowEndpoint(event.endpoint, completion_tx)); - // If the `NewAccessMethodEvent` should be announced to any client - // listening for updates of the currently active access method, we need - // to clone the handle to the broadcaster of such events. The - // announcement should be made after the firewall policy has been - // updated, since the new access method will be useless before then. - let event_listener = self.event_listener.clone(); - tokio::spawn(async move { - // Wait for the firewall policy to be updated. - let _ = completion_rx.await; - // Let the emitter of this event know that the firewall has been updated. - let _ = endpoint_active_tx.send(()); - // Notify clients about the change if necessary. - if event.announce { - event_listener.notify_new_access_method_event(event.setting); + match event { + AccessMethodEvent::Allow { endpoint } => { + let (completion_tx, completion_rx) = oneshot::channel(); + self.send_tunnel_command(TunnelCommand::AllowEndpoint(endpoint, completion_tx)); + tokio::spawn(async move { + // Wait for the firewall policy to be updated. + let _ = completion_rx.await; + // Let the emitter of this event know that the firewall has been updated. + let _ = endpoint_active_tx.send(()); + }); } - }); + AccessMethodEvent::New { setting, endpoint } => { + // Update the firewall to exempt a new API endpoint. + let (completion_tx, completion_rx) = oneshot::channel(); + self.send_tunnel_command(TunnelCommand::AllowEndpoint(endpoint, completion_tx)); + // Announce to all clients listening for updates of the + // currently active access method. The announcement should be + // made after the firewall policy has been updated, since the + // new access method will be useless before then. + let event_listener = self.event_listener.clone(); + tokio::spawn(async move { + // Wait for the firewall policy to be updated. + let _ = completion_rx.await; + // Let the emitter of this event know that the firewall has been updated. + let _ = endpoint_active_tx.send(()); + // Notify clients about the change if necessary. + event_listener.notify_new_access_method_event(setting); + }); + } + } } fn handle_device_migration_event( @@ -2496,10 +2507,11 @@ where let result = async move { // Send an internal daemon event which will punch a hole in the firewall // for the connection mode we are testing. - let _ = api::NewAccessMethodEvent::new(test_subject.setting, test_subject.endpoint) - .announce(false) - .send(daemon_event_sender.clone()) - .await; + let _ = api::AccessMethodEvent::Allow { + endpoint: test_subject.endpoint, + } + .send(daemon_event_sender.clone()) + .await; // Send a HEAD request to some Mullvad API endpoint. We issue a HEAD // request because we are *only* concerned with if we get a reply from @@ -2515,10 +2527,12 @@ where .get_current() .await .map_err(Error::ApiConnectionModeError)?; - let _ = api::NewAccessMethodEvent::new(active.setting, active.endpoint) - .announce(false) - .send(daemon_event_sender.clone()) - .await; + + let _ = api::AccessMethodEvent::Allow { + endpoint: active.endpoint, + } + .send(daemon_event_sender.clone()) + .await; result } diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs index 8334ecd1ee8b..a41b8d664310 100644 --- a/mullvad-jni/src/lib.rs +++ b/mullvad-jni/src/lib.rs @@ -307,11 +307,10 @@ fn api_endpoint_from_java(env: &JnixEnv<'_>, object: JObject<'_>) -> mullvad_api .l() .expect("ApiEndpoint.address is not an InetSocketAddress"); - endpoint.addr = - try_socketaddr_from_java(env, address).expect("received unresolved InetSocketAddress"); - if let Some(host) = try_hostname_from_java(env, address) { - endpoint.host = host; - } + endpoint.address = Some( + try_socketaddr_from_java(env, address).expect("received unresolved InetSocketAddress"), + ); + endpoint.host = try_hostname_from_java(env, address); endpoint.disable_address_cache = env .call_method(object, "component2", "()Z", &[]) .expect("missing ApiEndpoint.disableAddressCache") @@ -322,11 +321,6 @@ fn api_endpoint_from_java(env: &JnixEnv<'_>, object: JObject<'_>) -> mullvad_api .expect("missing ApiEndpoint.disableTls") .z() .expect("ApiEndpoint.disableTls is not a bool"); - endpoint.force_direct_connection = env - .call_method(object, "component4", "()Z", &[]) - .expect("missing ApiEndpoint.forceDirectConnection") - .z() - .expect("ApiEndpoint.forceDirectConnection is not a bool"); endpoint } diff --git a/test/test-manager/src/tests/account.rs b/test/test-manager/src/tests/account.rs index 8b30f8576850..d30d0330f062 100644 --- a/test/test-manager/src/tests/account.rs +++ b/test/test-manager/src/tests/account.rs @@ -221,7 +221,7 @@ pub async fn new_device_client() -> DevicesProxy { let api_endpoint = mullvad_api::ApiEndpoint::from_env_vars(); let api_host = format!("api.{}", TEST_CONFIG.mullvad_host); - let api_addr = format!("{api_host}:443") + let api_address = format!("{api_host}:443") .to_socket_addrs() .expect("failed to resolve API host") .next() @@ -229,8 +229,8 @@ pub async fn new_device_client() -> DevicesProxy { // Override the API endpoint to use the one specified in the test config let _ = mullvad_api::API.override_init(mullvad_api::ApiEndpoint { - host: api_host, - addr: api_addr, + host: Some(api_host), + address: Some(api_address), ..api_endpoint });