summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2022-06-07 15:16:31 +0200
committerDavid Lönnhager <david.l@mullvad.net>2022-06-14 13:59:49 +0200
commitf3acffca0c5da48c854658a19d11eed4104036b0 (patch)
tree8ca38b0a72ebc231b891c244faf52ebb11f9899f
parent463042e83845efd09f61cd99feb65db58240bb32 (diff)
downloadmullvadvpn-f3acffca0c5da48c854658a19d11eed4104036b0.tar.xz
mullvadvpn-f3acffca0c5da48c854658a19d11eed4104036b0.zip
Track excluded processes and add CLI command for listing them on Windows
-rw-r--r--mullvad-cli/src/cmds/split_tunnel/windows.rs41
-rw-r--r--mullvad-daemon/src/lib.rs17
-rw-r--r--mullvad-daemon/src/management_interface.rs36
-rw-r--r--mullvad-management-interface/proto/management_interface.proto11
-rw-r--r--talpid-core/src/split_tunnel/windows/mod.rs60
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs5
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs5
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs5
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs15
-rw-r--r--talpid-core/src/tunnel_state_machine/error_state.rs5
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs6
11 files changed, 203 insertions, 3 deletions
diff --git a/mullvad-cli/src/cmds/split_tunnel/windows.rs b/mullvad-cli/src/cmds/split_tunnel/windows.rs
index 0bc8d468bc..9766607ce1 100644
--- a/mullvad-cli/src/cmds/split_tunnel/windows.rs
+++ b/mullvad-cli/src/cmds/split_tunnel/windows.rs
@@ -1,3 +1,5 @@
+use std::{ffi::OsStr, path::Path};
+
use crate::{new_rpc_client, Command, Result};
pub struct SplitTunnel;
@@ -23,11 +25,13 @@ impl Command for SplitTunnel {
),
)
.subcommand(clap::App::new("get").about("Display the split tunnel status"))
+ .subcommand(create_pid_subcommand())
}
async fn run(&self, matches: &clap::ArgMatches) -> Result<()> {
match matches.subcommand() {
Some(("app", matches)) => Self::handle_app_subcommand(matches).await,
+ Some(("pid", matches)) => Self::handle_pid_subcommand(matches).await,
Some(("get", _)) => self.get().await,
Some(("set", matches)) => {
let enabled = matches.value_of("policy").expect("missing policy");
@@ -50,6 +54,16 @@ fn create_app_subcommand() -> clap::App<'static> {
.subcommand(clap::App::new("clear"))
}
+fn create_pid_subcommand() -> clap::App<'static> {
+ clap::App::new("pid")
+ .about("Manages processes (PIDs) excluded from the tunnel")
+ .setting(clap::AppSettings::SubcommandRequiredElseHelp)
+ .subcommand(clap::App::new("list")
+ .about("List processes that are currently being excluded, i.e. their PIDs, as well as whether \
+ they are excluded because of their executable paths or because they're subprocesses of \
+ such processes"))
+}
+
impl SplitTunnel {
async fn handle_app_subcommand(matches: &clap::ArgMatches) -> Result<()> {
match matches.subcommand() {
@@ -91,6 +105,33 @@ impl SplitTunnel {
}
}
+ async fn handle_pid_subcommand(matches: &clap::ArgMatches) -> Result<()> {
+ match matches.subcommand() {
+ Some(("list", _)) => {
+ let processes = new_rpc_client()
+ .await?
+ .get_excluded_processes(())
+ .await?
+ .into_inner();
+
+ for process in &processes.processes {
+ let subproc = if process.inherited { "subprocess" } else { "" };
+ println!(
+ "{:<7}{subproc:<12}{}",
+ process.pid,
+ Path::new(&process.image)
+ .file_name()
+ .unwrap_or(OsStr::new("unknown"))
+ .to_string_lossy()
+ );
+ }
+
+ Ok(())
+ }
+ _ => unreachable!("unhandled subcommand"),
+ }
+ }
+
async fn set(&self, enabled: bool) -> Result<()> {
let mut rpc = new_rpc_client().await?;
rpc.set_split_tunnel_state(enabled).await?;
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index c013361925..8dfcf67ace 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -265,9 +265,12 @@ pub enum DaemonCommand {
/// Clear list of apps to exclude from the tunnel
#[cfg(windows)]
ClearSplitTunnelApps(ResponseTx<(), Error>),
- /// Disable split tunnel
+ /// Enable or disable split tunneling
#[cfg(windows)]
SetSplitTunnelState(ResponseTx<(), Error>, bool),
+ /// Returns all processes currently being excluded from the tunnel
+ #[cfg(windows)]
+ GetSplitTunnelProcesses(ResponseTx<Vec<split_tunnel::ExcludedProcess>, split_tunnel::Error>),
/// Toggle wireguard-nt on or off
#[cfg(target_os = "windows")]
UseWireGuardNt(ResponseTx<(), Error>, bool),
@@ -1013,6 +1016,8 @@ where
ClearSplitTunnelApps(tx) => self.on_clear_split_tunnel_apps(tx).await,
#[cfg(windows)]
SetSplitTunnelState(tx, enabled) => self.on_set_split_tunnel_state(tx, enabled).await,
+ #[cfg(windows)]
+ GetSplitTunnelProcesses(tx) => self.on_get_split_tunnel_processes(tx),
#[cfg(target_os = "windows")]
UseWireGuardNt(tx, state) => self.on_use_wireguard_nt(tx, state).await,
#[cfg(target_os = "windows")]
@@ -1674,6 +1679,14 @@ where
}
#[cfg(windows)]
+ fn on_get_split_tunnel_processes(
+ &self,
+ tx: ResponseTx<Vec<split_tunnel::ExcludedProcess>, split_tunnel::Error>,
+ ) {
+ self.send_tunnel_command(TunnelCommand::GetExcludedProcesses(tx));
+ }
+
+ #[cfg(windows)]
async fn on_use_wireguard_nt(&mut self, tx: ResponseTx<(), Error>, state: bool) {
let save_result = self
.settings
@@ -2198,7 +2211,7 @@ where
}
}
- fn send_tunnel_command(&mut self, command: TunnelCommand) {
+ fn send_tunnel_command(&self, command: TunnelCommand) {
self.tunnel_command_tx
.unbounded_send(command)
.expect("Tunnel state machine has stopped");
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index a7478eb048..7999fd9c55 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -768,6 +768,42 @@ impl ManagementService for ManagementServiceImpl {
}
#[cfg(windows)]
+ async fn get_excluded_processes(
+ &self,
+ _: Request<()>,
+ ) -> ServiceResult<types::ExcludedProcessList> {
+ log::debug!("get_excluded_processes");
+ let (tx, rx) = oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::GetSplitTunnelProcesses(tx))?;
+ self.wait_for_result(rx)
+ .await?
+ .map_err(map_split_tunnel_error)
+ .map(|processes| {
+ Response::new(types::ExcludedProcessList {
+ processes: processes
+ .into_iter()
+ .map(|process| types::ExcludedProcess {
+ // FIXME: This is necessarily 32 bits or less
+ pid: u32::try_from(process.pid).unwrap(),
+ image: process.image.into_os_string().to_string_lossy().to_string(),
+ inherited: process.inherited,
+ })
+ .collect(),
+ })
+ })
+ }
+
+ #[cfg(not(windows))]
+ async fn get_excluded_processes(
+ &self,
+ _: Request<()>,
+ ) -> ServiceResult<types::ExcludedProcessList> {
+ Ok(Response::new(types::ExcludedProcessList {
+ processes: vec![],
+ }))
+ }
+
+ #[cfg(windows)]
async fn set_use_wireguard_nt(&self, request: Request<bool>) -> ServiceResult<()> {
log::debug!("set_use_wireguard_nt");
let state = request.into_inner();
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 9148a10150..2f39f496d6 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -79,6 +79,7 @@ service ManagementService {
rpc RemoveSplitTunnelApp(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc ClearSplitTunnelApps(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc SetSplitTunnelState(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
+ rpc GetExcludedProcesses(google.protobuf.Empty) returns (ExcludedProcessList) {}
rpc SetUseWireguardNt(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
@@ -476,6 +477,16 @@ message PublicKey {
google.protobuf.Timestamp created = 2;
}
+message ExcludedProcess {
+ uint32 pid = 1;
+ string image = 2;
+ bool inherited = 3;
+}
+
+message ExcludedProcessList {
+ repeated ExcludedProcess processes = 1;
+}
+
message AppVersionInfo {
bool supported = 1;
string latest_stable = 2;
diff --git a/talpid-core/src/split_tunnel/windows/mod.rs b/talpid-core/src/split_tunnel/windows/mod.rs
index 5b5b29e0cc..8b679106ea 100644
--- a/talpid-core/src/split_tunnel/windows/mod.rs
+++ b/talpid-core/src/split_tunnel/windows/mod.rs
@@ -12,15 +12,17 @@ use crate::{
};
use futures::channel::{mpsc, oneshot};
use std::{
+ collections::HashMap,
convert::TryFrom,
ffi::{OsStr, OsString},
io, mem,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
os::windows::io::{AsRawHandle, RawHandle},
+ path::{Path, PathBuf},
ptr,
sync::{
atomic::{AtomicBool, Ordering},
- mpsc as sync_mpsc, Arc, Mutex, Weak,
+ mpsc as sync_mpsc, Arc, Mutex, RwLock, Weak,
},
time::Duration,
};
@@ -102,6 +104,7 @@ pub struct SplitTunnel {
request_tx: RequestTx,
event_thread: Option<std::thread::JoinHandle<()>>,
quit_event: Arc<QuitEvent>,
+ excluded_pids: Arc<RwLock<HashMap<usize, ExcludedProcess>>>,
_route_change_callback: Option<WinNetCallbackHandle>,
daemon_tx: Weak<mpsc::UnboundedSender<TunnelCommand>>,
async_path_update_in_progress: Arc<AtomicBool>,
@@ -148,10 +151,23 @@ struct InterfaceAddresses {
internet_ipv6: Option<Ipv6Addr>,
}
+/// Represents a process that is being excluded from the tunnel.
+#[derive(Debug, Clone)]
+pub struct ExcludedProcess {
+ /// Process identifier.
+ pub pid: u32,
+ /// Path to the image that this process is an instance of.
+ pub image: PathBuf,
+ /// If true, then the process is split because its parent was split,
+ /// not due to its path being in the config.
+ pub inherited: bool,
+}
+
struct EventThreadContext {
handle: Arc<driver::DeviceHandle>,
event_overlapped: OVERLAPPED,
quit_event: Arc<QuitEvent>,
+ excluded_pids: Arc<RwLock<HashMap<usize, ExcludedProcess>>>,
}
unsafe impl Send for EventThreadContext {}
@@ -172,11 +188,13 @@ impl SplitTunnel {
}
let quit_event = Arc::new(QuitEvent::new());
+ let excluded_pids = Arc::new(RwLock::new(HashMap::new()));
let event_context = EventThreadContext {
handle: handle.clone(),
event_overlapped,
quit_event: quit_event.clone(),
+ excluded_pids: excluded_pids.clone(),
};
let event_thread = std::thread::spawn(move || {
@@ -291,6 +309,34 @@ impl SplitTunnel {
reason,
image,
} => {
+ let mut pids = event_context.excluded_pids.write().unwrap();
+ match event_id {
+ EventId::StartSplittingProcess => {
+ if let Some(prev_entry) = pids.get(&process_id) {
+ log::error!("PID collision: {process_id} is already in the list of excluded processes. New image: {:?}. Current image: {:?}", image, prev_entry);
+ }
+ pids.insert(
+ process_id,
+ ExcludedProcess {
+ pid: u32::try_from(process_id)
+ .expect("PID should be containable in a DWORD"),
+ image: Path::new(&image).to_path_buf(),
+ inherited: reason.contains(
+ driver::SplittingChangeReason::BY_INHERITANCE,
+ ),
+ },
+ );
+ }
+ EventId::StopSplittingProcess => {
+ if pids.remove(&process_id).is_none() {
+ log::error!(
+ "Inconsistent process tree: {process_id} was not found"
+ );
+ }
+ }
+ _ => (),
+ }
+
log::trace!(
"{}:\n\tpid: {}\n\treason: {:?}\n\timage: {:?}",
event_str,
@@ -326,6 +372,7 @@ impl SplitTunnel {
_route_change_callback: None,
daemon_tx,
async_path_update_in_progress: Arc::new(AtomicBool::new(false)),
+ excluded_pids,
})
}
@@ -562,6 +609,17 @@ impl SplitTunnel {
self._route_change_callback = None;
self.send_request(Request::RegisterIps(InterfaceAddresses::default()))
}
+
+ /// Return processes that are currently being excluded.
+ pub fn get_processes(&self) -> Result<Vec<ExcludedProcess>, Error> {
+ Ok(self
+ .excluded_pids
+ .read()
+ .unwrap()
+ .values()
+ .cloned()
+ .collect())
+ }
}
impl Drop for SplitTunnel {
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index 80e5e28957..beb29590c1 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -277,6 +277,11 @@ impl ConnectedState {
shared_values.split_tunnel.set_paths(&paths, result_tx);
SameState(self.into())
}
+ #[cfg(windows)]
+ Some(TunnelCommand::GetExcludedProcesses(result_tx)) => {
+ let _ = result_tx.send(shared_values.split_tunnel.get_processes());
+ SameState(self.into())
+ }
}
}
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index 7536b26b09..64d21086cd 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -390,6 +390,11 @@ impl ConnectingState {
shared_values.split_tunnel.set_paths(&paths, result_tx);
SameState(self.into())
}
+ #[cfg(windows)]
+ Some(TunnelCommand::GetExcludedProcesses(result_tx)) => {
+ let _ = result_tx.send(shared_values.split_tunnel.get_processes());
+ SameState(self.into())
+ }
}
}
diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
index fa91284f6b..281b22b8f2 100644
--- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
@@ -208,6 +208,11 @@ impl TunnelState for DisconnectedState {
shared_values.split_tunnel.set_paths(&paths, result_tx);
SameState(self.into())
}
+ #[cfg(windows)]
+ Some(TunnelCommand::GetExcludedProcesses(result_tx)) => {
+ let _ = result_tx.send(shared_values.split_tunnel.get_processes());
+ SameState(self.into())
+ }
None => {
Self::reset_dns(shared_values);
Finished
diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
index 2a14d881d6..2c42a1a864 100644
--- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
@@ -57,6 +57,11 @@ impl DisconnectingState {
shared_values.split_tunnel.set_paths(&paths, result_tx);
AfterDisconnect::Nothing
}
+ #[cfg(windows)]
+ Some(TunnelCommand::GetExcludedProcesses(result_tx)) => {
+ let _ = result_tx.send(shared_values.split_tunnel.get_processes());
+ AfterDisconnect::Nothing
+ }
},
AfterDisconnect::Block(reason) => match command {
Some(TunnelCommand::AllowLan(allow_lan)) => {
@@ -97,6 +102,11 @@ impl DisconnectingState {
shared_values.split_tunnel.set_paths(&paths, result_tx);
AfterDisconnect::Block(reason)
}
+ #[cfg(windows)]
+ Some(TunnelCommand::GetExcludedProcesses(result_tx)) => {
+ let _ = result_tx.send(shared_values.split_tunnel.get_processes());
+ AfterDisconnect::Block(reason)
+ }
None => AfterDisconnect::Block(reason),
},
AfterDisconnect::Reconnect(retry_attempt) => match command {
@@ -138,6 +148,11 @@ impl DisconnectingState {
shared_values.split_tunnel.set_paths(&paths, result_tx);
AfterDisconnect::Reconnect(retry_attempt)
}
+ #[cfg(windows)]
+ Some(TunnelCommand::GetExcludedProcesses(result_tx)) => {
+ let _ = result_tx.send(shared_values.split_tunnel.get_processes());
+ AfterDisconnect::Reconnect(retry_attempt)
+ }
},
};
diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs
index a11e49d859..d64393a432 100644
--- a/talpid-core/src/tunnel_state_machine/error_state.rs
+++ b/talpid-core/src/tunnel_state_machine/error_state.rs
@@ -209,6 +209,11 @@ impl TunnelState for ErrorState {
shared_values.split_tunnel.set_paths(&paths, result_tx);
SameState(self.into())
}
+ #[cfg(windows)]
+ Some(TunnelCommand::GetExcludedProcesses(result_tx)) => {
+ let _ = result_tx.send(shared_values.split_tunnel.get_processes());
+ SameState(self.into())
+ }
}
}
}
diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs
index 6c388a4680..42d20181a4 100644
--- a/talpid-core/src/tunnel_state_machine/mod.rs
+++ b/talpid-core/src/tunnel_state_machine/mod.rs
@@ -189,6 +189,12 @@ pub enum TunnelCommand {
oneshot::Sender<Result<(), split_tunnel::Error>>,
Vec<OsString>,
),
+ /// Return a list of processes that are currently being split, as well as their
+ /// paths.
+ #[cfg(windows)]
+ GetExcludedProcesses(
+ oneshot::Sender<Result<Vec<split_tunnel::ExcludedProcess>, split_tunnel::Error>>,
+ ),
}
type TunnelCommandReceiver = stream::Fuse<mpsc::UnboundedReceiver<TunnelCommand>>;