Skip to content

Commit

Permalink
Add Android support for managing split-tunneling state in the daemon
Browse files Browse the repository at this point in the history
Unify split tunnel code for Android and Windows
  • Loading branch information
MarkusPettersson98 committed Apr 25, 2024
1 parent accdb25 commit c524629
Show file tree
Hide file tree
Showing 17 changed files with 345 additions and 112 deletions.
118 changes: 80 additions & 38 deletions mullvad-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ mod geoip;
pub mod logging;
#[cfg(target_os = "macos")]
mod macos;
//#[cfg(not(target_os = "android"))]
pub mod management_interface;
mod migrations;
mod relay_list;
Expand Down Expand Up @@ -43,6 +42,8 @@ use mullvad_relay_selector::{
};
#[cfg(target_os = "android")]
use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
#[cfg(any(windows, target_os = "android"))]
use mullvad_types::settings::SplitApp;
#[cfg(target_os = "windows")]
use mullvad_types::wireguard::DaitaSettings;
use mullvad_types::{
Expand All @@ -63,10 +64,10 @@ use mullvad_types::{
};
use relay_list::{RelayListUpdater, RelayListUpdaterHandle, RELAYS_FILENAME};
use settings::SettingsPersister;
#[cfg(any(windows, target_os = "android"))]
use std::collections::HashSet;
#[cfg(target_os = "android")]
use std::os::unix::io::RawFd;
#[cfg(target_os = "windows")]
use std::{collections::HashSet, ffi::OsString};
use std::{
marker::PhantomData,
mem,
Expand All @@ -75,7 +76,7 @@ use std::{
sync::{Arc, Weak},
time::Duration,
};
#[cfg(any(target_os = "linux", windows))]
#[cfg(any(target_os = "linux", windows, target_os = "android"))]
use talpid_core::split_tunnel;
use talpid_core::{
mpsc::Sender,
Expand Down Expand Up @@ -147,7 +148,7 @@ pub enum Error {
#[error("Unable to initialize split tunneling")]
InitSplitTunneling(#[source] split_tunnel::Error),

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
#[error("Split tunneling error")]
SplitTunnelError(#[source] split_tunnel::Error),

Expand Down Expand Up @@ -331,16 +332,27 @@ pub enum DaemonCommand {
#[cfg(target_os = "linux")]
ClearSplitTunnelProcesses(ResponseTx<(), split_tunnel::Error>),
/// Exclude traffic of an application from the tunnel
#[cfg(windows)]
AddSplitTunnelApp(ResponseTx<(), Error>, PathBuf),
#[cfg(any(windows, target_os = "android"))]
AddSplitTunnelApp(ResponseTx<(), Error>, SplitApp),
/// Add a set of apps that should be excluded from the tunnel without triggering multiple
/// tunnel reconnets.
///
/// This command is only really useful for migrating old Android apps where split-tunneling
/// settings where stored client-side to instead let the daemon handle all state around split
/// apps.
///
/// FIXME: Deprecate + remove this command when all old app settings have been migrated.
/// Make sure to deprecate + remove the corresponding protobuf API.
#[cfg(target_os = "android")]
SetSplitTunnelApps(ResponseTx<(), Error>, Vec<SplitApp>),
/// Remove application from list of apps to exclude from the tunnel
#[cfg(windows)]
RemoveSplitTunnelApp(ResponseTx<(), Error>, PathBuf),
#[cfg(any(windows, target_os = "android"))]
RemoveSplitTunnelApp(ResponseTx<(), Error>, SplitApp),
/// Clear list of apps to exclude from the tunnel
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
ClearSplitTunnelApps(ResponseTx<(), Error>),
/// Enable or disable split tunneling
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
SetSplitTunnelState(ResponseTx<(), Error>, bool),
/// Returns all processes currently being excluded from the tunnel
#[cfg(windows)]
Expand Down Expand Up @@ -392,14 +404,14 @@ pub(crate) enum InternalDaemonEvent {
/// A geographical location has has been received from am.i.mullvad.net
LocationEvent(LocationEventData),
/// The split tunnel paths or state were updated.
#[cfg(target_os = "windows")]
#[cfg(any(windows, target_os = "android"))]
ExcludedPathsEvent(ExcludedPathsUpdate, oneshot::Sender<Result<(), Error>>),
}

#[cfg(target_os = "windows")]
#[cfg(any(windows, target_os = "android"))]
pub(crate) enum ExcludedPathsUpdate {
SetState(bool),
SetPaths(HashSet<PathBuf>),
SetPaths(HashSet<SplitApp>),
}

impl From<TunnelStateTransition> for InternalDaemonEvent {
Expand Down Expand Up @@ -781,13 +793,14 @@ where
PersistentTargetState::new(&cache_dir).await
};

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
let exclude_paths = if settings.split_tunnel.enable_exclusions {
settings
.split_tunnel
.apps
.iter()
.map(OsString::from)
.cloned()
.map(SplitApp::to_tunnel_command_repr)
.collect()
} else {
vec![]
Expand Down Expand Up @@ -824,7 +837,7 @@ where
.map_err(Error::ApiConnectionModeError)?
.endpoint,
reset_firewall: *target_state != TargetState::Secured,
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
exclude_paths,
},
parameters_generator.clone(),
Expand Down Expand Up @@ -1009,7 +1022,7 @@ where
} => self.handle_access_method_event(event, endpoint_active_tx),
DeviceMigrationEvent(event) => self.handle_device_migration_event(event),
LocationEvent(location_data) => self.handle_location_event(location_data),
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
ExcludedPathsEvent(update, tx) => self.handle_new_excluded_paths(update, tx).await,
}
}
Expand Down Expand Up @@ -1288,13 +1301,15 @@ where
RemoveSplitTunnelProcess(tx, pid) => self.on_remove_split_tunnel_process(tx, pid),
#[cfg(target_os = "linux")]
ClearSplitTunnelProcesses(tx) => self.on_clear_split_tunnel_processes(tx),
#[cfg(windows)]
AddSplitTunnelApp(tx, path) => self.on_add_split_tunnel_app(tx, path),
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
AddSplitTunnelApp(tx, app) => self.on_add_split_tunnel_app(tx, app),
#[cfg(target_os = "android")]
SetSplitTunnelApps(tx, apps) => self.on_set_split_tunnel_apps(tx, apps.into_iter()),
#[cfg(any(windows, target_os = "android"))]
RemoveSplitTunnelApp(tx, path) => self.on_remove_split_tunnel_app(tx, path),
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
ClearSplitTunnelApps(tx) => self.on_clear_split_tunnel_apps(tx),
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
SetSplitTunnelState(tx, enabled) => self.on_set_split_tunnel_state(tx, enabled),
#[cfg(windows)]
GetSplitTunnelProcesses(tx) => self.on_get_split_tunnel_processes(tx),
Expand Down Expand Up @@ -1450,7 +1465,7 @@ where
});
}

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
async fn handle_new_excluded_paths(
&mut self,
update: ExcludedPathsUpdate,
Expand Down Expand Up @@ -1823,7 +1838,7 @@ where
}

/// Update the split app paths in both the settings and tunnel
#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
fn set_split_tunnel_paths(
&mut self,
tx: ResponseTx<(), Error>,
Expand Down Expand Up @@ -1852,9 +1867,13 @@ where
}
};

// Update the tunnel state
if new_state || new_state != settings.split_tunnel.enable_exclusions {
let tunnel_list = if new_state {
new_list.map(OsString::from).collect()
new_list
.cloned()
.map(SplitApp::to_tunnel_command_repr)
.collect()
} else {
vec![]
};
Expand Down Expand Up @@ -1889,37 +1908,60 @@ where
}
}

#[cfg(windows)]
fn on_add_split_tunnel_app(&mut self, tx: ResponseTx<(), Error>, path: PathBuf) {
#[cfg(any(windows, target_os = "android"))]
fn on_add_split_tunnel_app(&mut self, tx: ResponseTx<(), Error>, app: impl Into<SplitApp>) {
let settings = self.settings.to_settings();

let mut new_list = settings.split_tunnel.apps.clone();
new_list.insert(path);
let excluded_apps = {
let mut apps = settings.split_tunnel.apps.clone();
apps.insert(app.into());
apps
};

self.set_split_tunnel_paths(
tx,
"add_split_tunnel_app response",
settings,
ExcludedPathsUpdate::SetPaths(new_list),
ExcludedPathsUpdate::SetPaths(excluded_apps),
);
}

#[cfg(windows)]
fn on_remove_split_tunnel_app(&mut self, tx: ResponseTx<(), Error>, path: PathBuf) {
#[cfg(target_os = "android")]
fn on_set_split_tunnel_apps(
&mut self,
tx: ResponseTx<(), Error>,
apps: impl Iterator<Item = impl Into<SplitApp>>,
) {
let settings = self.settings.to_settings();
let excluded_apps = apps.map(|app| app.into()).collect();

let mut new_list = settings.split_tunnel.apps.clone();
new_list.remove(&path);
self.set_split_tunnel_paths(
tx,
"add_split_tunnel_apps response",
settings,
ExcludedPathsUpdate::SetPaths(excluded_apps),
);
}

#[cfg(any(windows, target_os = "android"))]
fn on_remove_split_tunnel_app(&mut self, tx: ResponseTx<(), Error>, app: impl Into<SplitApp>) {
let settings = self.settings.to_settings();

let excluded_apps = {
let mut apps = settings.split_tunnel.apps.clone();
apps.remove(&app.into());
apps
};

self.set_split_tunnel_paths(
tx,
"remove_split_tunnel_app response",
settings,
ExcludedPathsUpdate::SetPaths(new_list),
ExcludedPathsUpdate::SetPaths(excluded_apps),
);
}

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
fn on_clear_split_tunnel_apps(&mut self, tx: ResponseTx<(), Error>) {
let settings = self.settings.to_settings();
let new_list = HashSet::new();
Expand All @@ -1931,7 +1973,7 @@ where
);
}

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
fn on_set_split_tunnel_state(&mut self, tx: ResponseTx<(), Error>, state: bool) {
let settings = self.settings.to_settings();
self.set_split_tunnel_paths(
Expand Down
55 changes: 43 additions & 12 deletions mullvad-daemon/src/management_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ use mullvad_types::{
version,
wireguard::{RotationInterval, RotationIntervalError},
};
#[cfg(windows)]
use std::path::PathBuf;
use std::{
str::FromStr,
sync::{Arc, Mutex},
Expand Down Expand Up @@ -824,39 +822,43 @@ impl ManagementService for ManagementServiceImpl {
}
}

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
async fn add_split_tunnel_app(&self, request: Request<String>) -> ServiceResult<()> {
use mullvad_types::settings::SplitApp;
log::debug!("add_split_tunnel_app");
let path = PathBuf::from(request.into_inner());
let path = SplitApp::from(request.into_inner());
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::AddSplitTunnelApp(tx, path))?;
self.wait_for_result(rx)
.await?
.map_err(map_daemon_error)
.map(Response::new)
}
#[cfg(not(windows))]

#[cfg(not(any(windows, target_os = "android")))]
async fn add_split_tunnel_app(&self, _: Request<String>) -> ServiceResult<()> {
Ok(Response::new(()))
}

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
async fn remove_split_tunnel_app(&self, request: Request<String>) -> ServiceResult<()> {
use mullvad_types::settings::SplitApp;
log::debug!("remove_split_tunnel_app");
let path = PathBuf::from(request.into_inner());
let path = SplitApp::from(request.into_inner());
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::RemoveSplitTunnelApp(tx, path))?;
self.wait_for_result(rx)
.await?
.map_err(map_daemon_error)
.map(Response::new)
}
#[cfg(not(windows))]

#[cfg(not(any(windows, target_os = "android")))]
async fn remove_split_tunnel_app(&self, _: Request<String>) -> ServiceResult<()> {
Ok(Response::new(()))
}

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
async fn clear_split_tunnel_apps(&self, _: Request<()>) -> ServiceResult<()> {
log::debug!("clear_split_tunnel_apps");
let (tx, rx) = oneshot::channel();
Expand All @@ -866,12 +868,13 @@ impl ManagementService for ManagementServiceImpl {
.map_err(map_daemon_error)
.map(Response::new)
}
#[cfg(not(windows))]

#[cfg(not(any(windows, target_os = "android")))]
async fn clear_split_tunnel_apps(&self, _: Request<()>) -> ServiceResult<()> {
Ok(Response::new(()))
}

#[cfg(windows)]
#[cfg(any(windows, target_os = "android"))]
async fn set_split_tunnel_state(&self, request: Request<bool>) -> ServiceResult<()> {
log::debug!("set_split_tunnel_state");
let enabled = request.into_inner();
Expand All @@ -882,11 +885,39 @@ impl ManagementService for ManagementServiceImpl {
.map_err(map_daemon_error)
.map(Response::new)
}
#[cfg(not(windows))]

#[cfg(not(any(windows, target_os = "android")))]
async fn set_split_tunnel_state(&self, _: Request<bool>) -> ServiceResult<()> {
Ok(Response::new(()))
}

#[cfg(target_os = "android")]
async fn set_split_tunnel_apps(
&self,
request: Request<types::ExcludedApps>,
) -> ServiceResult<()> {
use mullvad_types::settings::SplitApp;
log::debug!("set_split_tunnel_apps");
let excluded_apps = request
.into_inner()
.apps
.into_iter()
.map(SplitApp::from)
.collect();

let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetSplitTunnelApps(tx, excluded_apps))?;
self.wait_for_result(rx)
.await?
.map_err(map_daemon_error)
.map(Response::new)
}

#[cfg(not(target_os = "android"))]
async fn set_split_tunnel_apps(&self, _: Request<types::ExcludedApps>) -> ServiceResult<()> {
Ok(Response::new(()))
}

#[cfg(windows)]
async fn get_excluded_processes(
&self,
Expand Down

0 comments on commit c524629

Please sign in to comment.