Skip to content

Commit

Permalink
Merge branch 'fix-connectivity-tests-ios-873'
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet committed Nov 26, 2024
2 parents b0ddb2d + 93fcbf1 commit 795ce07
Show file tree
Hide file tree
Showing 15 changed files with 77 additions and 16 deletions.
5 changes: 5 additions & 0 deletions ios/Configurations/Api.xcconfig.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ API_HOST_NAME[config=Release] = api.$(HOST_NAME)
API_HOST_NAME[config=MockRelease] = api.$(HOST_NAME)
API_HOST_NAME[config=Staging] = api.$(HOST_NAME)

ENCRYPTED_DNS_HOST_NAME[config=Debug] = frakta.eu
ENCRYPTED_DNS_HOST_NAME[config=Release] = frakta.eu
ENCRYPTED_DNS_HOST_NAME[config=MockRelease] = frakta.eu
ENCRYPTED_DNS_HOST_NAME[config=Staging] = stagemole.frakta.eu

API_ENDPOINT[config=Debug] = 45.83.223.196:443
API_ENDPOINT[config=Release] = 45.83.223.196:443
API_ENDPOINT[config=MockRelease] = 45.83.223.196:443
Expand Down
2 changes: 2 additions & 0 deletions ios/MullvadREST/ApiHandlers/RESTDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ extension REST {
/// Default API endpoint.
public static let defaultAPIEndpoint = AnyIPEndpoint(string: infoDictionary["ApiEndpoint"] as! String)!

public static let encryptedDNSHostname = infoDictionary["EncryptedDnsHostName"] as! String

/// Disables API IP address cache when in staging environment and sticks to using default API endpoint instead.
public static let isStagingEnvironment = false

Expand Down
2 changes: 2 additions & 0 deletions ios/MullvadREST/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
<string>$(API_HOST_NAME)</string>
<key>ApiEndpoint</key>
<string>$(API_ENDPOINT)</string>
<key>EncryptedDnsHostName</key>
<string>$(ENCRYPTED_DNS_HOST_NAME)</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public final class EncryptedDNSTransport: RESTTransport {

public init(urlSession: URLSession) {
self.urlSession = urlSession
self.encryptedDnsProxy = EncryptedDNSProxy()
self.encryptedDnsProxy = EncryptedDNSProxy(domain: REST.encryptedDNSHostname)
}

public func stop() {
Expand Down
6 changes: 4 additions & 2 deletions ios/MullvadRustRuntime/EncryptedDNSProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ public class EncryptedDNSProxy {
private var stateLock = NSLock()
private var didStart = false
private let state: OpaquePointer
private let domain: String

public init() {
state = encrypted_dns_proxy_init()
public init(domain: String) {
self.domain = domain
state = encrypted_dns_proxy_init(domain)
proxyConfig = ProxyHandle(context: nil, port: 0)
}

Expand Down
11 changes: 10 additions & 1 deletion ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@ extern const uint16_t CONFIG_SERVICE_PORT;

/**
* Initializes a valid pointer to an instance of `EncryptedDnsProxyState`.
*
* # Safety
*
* * [domain_name] must not be non-null.
*
* * [domain_name] pointer must be [valid](core::ptr#safety)
*
* * The caller must ensure that the pointer to the [domain_name] string contains a nul terminator
* at the end of the string.
*/
struct EncryptedDnsProxyState *encrypted_dns_proxy_init(void);
struct EncryptedDnsProxyState *encrypted_dns_proxy_init(const char *domain_name);

/**
* This must be called only once to deallocate `EncryptedDnsProxyState`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ class EditAccessMethodViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

view.accessibilityIdentifier = .editAccessMethodView
view.backgroundColor = .secondaryColor

tableView.accessibilityIdentifier = .editAccessMethodView
tableView.backgroundColor = .secondaryColor
tableView.delegate = self

Expand Down
7 changes: 5 additions & 2 deletions ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,19 @@ final class TransportMonitor: RESTTransportProvider {
tunnel.status == .connecting || tunnel.status == .reasserting || tunnel.status == .connected
}

if let tunnel, shouldByPassVPN(tunnel: tunnel) {
if let tunnel, shouldBypassVPN(tunnel: tunnel) {
return PacketTunnelTransport(tunnel: tunnel)
} else {
return transportProvider.makeTransport()
}
}

private func shouldByPassVPN(tunnel: any TunnelProtocol) -> Bool {
private func shouldBypassVPN(tunnel: any TunnelProtocol) -> Bool {
switch tunnel.status {
case .connected:
if case .error = tunnelManager.tunnelStatus.state {
return true
}
return tunnelManager.isConfigurationLoaded && tunnelManager.deviceState == .revoked

case .connecting, .reasserting:
Expand Down
2 changes: 2 additions & 0 deletions ios/MullvadVPNTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EncryptedDnsHostName</key>
<string>$(ENCRYPTED_DNS_HOST_NAME)</string>
<key>ApiHostName</key>
<string>$(API_HOST_NAME)</string>
<key>ApiEndpoint</key>
Expand Down
11 changes: 11 additions & 0 deletions ios/MullvadVPNUITests/ConnectivityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ class ConnectivityTests: LoggedOutUITestCase {

/// Verifies that the app still functions when API has been blocked
func testAPIConnectionViaBridges() throws {
let skipReason = """
This test is currently skipped because shadowsocks bridges cannot be reached
from the staging environment
"""
try XCTSkipIf(true, skipReason)
firewallAPIClient.removeRules()
let hasTimeAccountNumber = getAccountWithTime()

Expand Down Expand Up @@ -138,6 +143,12 @@ class ConnectivityTests: LoggedOutUITestCase {
// swiftlint:disable function_body_length
/// Test that the app is functioning when API is down. To simulate API being down we create a dummy access method
func testAppStillFunctioningWhenAPIDown() throws {
let skipReason = """
This test is currently skipped due to a bug in iOS 18 where ATS shuts down the
connection to the API in the blocked state, despite being explicitly disabled,
and after the checks in SSLPinningURLSessionDelegate return no error.
"""
try XCTSkipIf(true, skipReason)
let hasTimeAccountNumber = getAccountWithTime()

addTeardownBlock {
Expand Down
2 changes: 2 additions & 0 deletions ios/MullvadVPNUITests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<string>$(API_ENDPOINT)</string>
<key>ApiHostName</key>
<string>$(API_HOST_NAME)</string>
<key>EncryptedDnsHostName</key>
<string>$(ENCRYPTED_DNS_HOST_NAME)</string>
<key>AttachAppLogsOnFailure</key>
<string>$(ATTACH_APP_LOGS_ON_FAILURE)</string>
<key>DisplayName</key>
Expand Down
2 changes: 1 addition & 1 deletion mullvad-daemon/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ impl AccessModeSelector {
ApiConnectionMode::Proxied(ProxyConfig::from(proxy))
}
AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => {
if let Err(error) = encrypted_dns_proxy_cache.fetch_configs().await {
if let Err(error) = encrypted_dns_proxy_cache.fetch_configs("frakta.eu").await {
log::warn!("Failed to fetch new Encrypted DNS Proxy configurations");
log::debug!("{error:#?}");
}
Expand Down
7 changes: 4 additions & 3 deletions mullvad-encrypted-dns-proxy/src/config_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ pub fn default_resolvers() -> Vec<Nameserver> {
]
}

pub async fn resolve_default_config() -> Result<Vec<config::ProxyConfig>, Error> {
resolve_configs(&default_resolvers(), "frakta.eu").await
/// Calls [resolve_configs] with a given `domain` using known DoH resolvers provided by [default_resolvers]
pub async fn resolve_default_config(domain: &str) -> Result<Vec<config::ProxyConfig>, Error> {
resolve_configs(&default_resolvers(), domain).await
}

/// Look up the `domain` towards the given `resolvers`, and try to deserialize all the returned
/// Looks up the `domain` towards the given `resolvers`, and try to deserialize all the returned
/// AAAA records into [`ProxyConfig`](config::ProxyConfig)s.
pub async fn resolve_configs(
resolvers: &[Nameserver],
Expand Down
6 changes: 3 additions & 3 deletions mullvad-encrypted-dns-proxy/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ impl EncryptedDnsProxyState {
Some(selected_config)
}

/// Fetch a config, but error out only when no existing configuration was there.
pub async fn fetch_configs(&mut self) -> Result<(), FetchConfigError> {
match resolve_default_config().await {
/// Fetch a config from `domain`, but error out only when no existing configuration was there.
pub async fn fetch_configs(&mut self, domain: &str) -> Result<(), FetchConfigError> {
match resolve_default_config(domain).await {
Ok(new_configs) => {
self.configurations = HashSet::from_iter(new_configs.into_iter());
}
Expand Down
26 changes: 24 additions & 2 deletions mullvad-ios/src/encrypted_dns_proxy.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::ProxyHandle;

use libc::c_char;
use mullvad_encrypted_dns_proxy::state::{EncryptedDnsProxyState as State, FetchConfigError};
use mullvad_encrypted_dns_proxy::Forwarder;
use std::{
Expand All @@ -9,10 +10,13 @@ use std::{
};
use tokio::{net::TcpListener, task::JoinHandle};

use std::ffi::CStr;

/// A thin wrapper around [`mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState`] that
/// can start a local forwarder (see [`Self::start`]).
pub struct EncryptedDnsProxyState {
state: State,
domain: String,
}

#[derive(Debug)]
Expand Down Expand Up @@ -47,7 +51,7 @@ impl From<Error> for i32 {
impl EncryptedDnsProxyState {
async fn start(&mut self) -> Result<ProxyHandle, Error> {
self.state
.fetch_configs()
.fetch_configs(&self.domain)
.await
.map_err(Error::FetchConfig)?;
let proxy_configuration = self.state.next_configuration().ok_or(Error::NoConfigs)?;
Expand Down Expand Up @@ -78,10 +82,28 @@ impl EncryptedDnsProxyState {
}

/// Initializes a valid pointer to an instance of `EncryptedDnsProxyState`.
///
/// # Safety
///
/// * [domain_name] must not be non-null.
///
/// * [domain_name] pointer must be [valid](core::ptr#safety)
///
/// * The caller must ensure that the pointer to the [domain_name] string contains a nul terminator
/// at the end of the string.
#[no_mangle]
pub unsafe extern "C" fn encrypted_dns_proxy_init() -> *mut EncryptedDnsProxyState {
pub unsafe extern "C" fn encrypted_dns_proxy_init(
domain_name: *const c_char,
) -> *mut EncryptedDnsProxyState {
let domain = {
// SAFETY: domain_name points to a valid region of memory and contains a nul terminator.
let c_str = unsafe { CStr::from_ptr(domain_name) };
String::from_utf8_lossy(c_str.to_bytes())
};

let state = Box::new(EncryptedDnsProxyState {
state: State::default(),
domain: domain.into_owned(),
});
Box::into_raw(state)
}
Expand Down

0 comments on commit 795ce07

Please sign in to comment.