mirror of
https://github.com/void-linux/void-packages.git
synced 2025-08-01 18:32:58 +02:00
mullvad: add patch for cgroups v1
This commit is contained in:
parent
b89a4eb48e
commit
ed01e2f7df
4 changed files with 382 additions and 1 deletions
|
@ -5,7 +5,6 @@ export MULLVAD_LOG_DIR=/var/log/mullvad-vpn
|
|||
export MULLVAD_SETTINGS_DIR=/etc/mullvad-vpn
|
||||
export MULLVAD_CACHE_DIR=/var/cache/mullvad-vpn
|
||||
export MULLVAD_RPC_SOCKET_PATH=/run/mullvad-vpn/mullvad
|
||||
export TALPID_NET_CLS_MOUNT_DIR=/run/mullvad-vpn/cgroup
|
||||
export MULLVAD_MANAGEMENT_SOCKET_GROUP=_mullvad
|
||||
|
||||
# needs cap_dac_override to write /etc/resolv.conf{,.mullvadbackup}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
From 267087b3255e2748693d52728dadfdd69c6cb761 Mon Sep 17 00:00:00 2001
|
||||
From: Joakim Hulthe <joakim.hulthe@mullvad.net>
|
||||
Date: Thu, 19 Jun 2025 15:22:53 +0200
|
||||
Subject: [PATCH] Do not add split-tunneling fw rules if no net_cls
|
||||
|
||||
---
|
||||
talpid-core/src/firewall/linux.rs | 31 +++++++++++++++++++++++++++----
|
||||
1 file changed, 27 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs
|
||||
index 26d3a5cb5a68..b72b4c12d6ea 100644
|
||||
--- a/talpid-core/src/firewall/linux.rs
|
||||
+++ b/talpid-core/src/firewall/linux.rs
|
||||
@@ -12,9 +12,12 @@ use std::{
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
sync::LazyLock,
|
||||
};
|
||||
-use talpid_types::net::{
|
||||
- AllowedEndpoint, AllowedTunnelTraffic, Endpoint, TransportProtocol, ALLOWED_LAN_MULTICAST_NETS,
|
||||
- ALLOWED_LAN_NETS,
|
||||
+use talpid_types::{
|
||||
+ cgroup::find_net_cls_mount,
|
||||
+ net::{
|
||||
+ AllowedEndpoint, AllowedTunnelTraffic, Endpoint, TransportProtocol,
|
||||
+ ALLOWED_LAN_MULTICAST_NETS, ALLOWED_LAN_NETS,
|
||||
+ },
|
||||
};
|
||||
|
||||
/// Priority for rules that tag split tunneling packets. Equals NF_IP_PRI_MANGLE.
|
||||
@@ -52,6 +55,10 @@ pub enum Error {
|
||||
/// Unable to translate network interface name into index.
|
||||
#[error("Unable to translate network interface name \"{0}\" into index")]
|
||||
LookupIfaceIndexError(String, #[source] crate::linux::IfaceIndexLookupError),
|
||||
+
|
||||
+ /// Failed to check if the net_cls mount exists.
|
||||
+ #[error("An error occurred when checking for net_cls")]
|
||||
+ FindNetClsMount(#[source] io::Error),
|
||||
}
|
||||
|
||||
/// TODO(linus): This crate is not supposed to be Mullvad-aware. So at some point this should be
|
||||
@@ -303,7 +310,19 @@ impl<'a> PolicyBatch<'a> {
|
||||
/// policy.
|
||||
pub fn finalize(mut self, policy: &FirewallPolicy, fwmark: u32) -> Result<FinalizedBatch> {
|
||||
self.add_loopback_rules()?;
|
||||
- self.add_split_tunneling_rules(policy, fwmark)?;
|
||||
+
|
||||
+ // if cgroups v1 doesn't exist, split tunneling won't work.
|
||||
+ // checking if the `net_cls` mount exists is a cheeky way of checking this.
|
||||
+ if find_net_cls_mount()
|
||||
+ .map_err(Error::FindNetClsMount)?
|
||||
+ .is_some()
|
||||
+ {
|
||||
+ self.add_split_tunneling_rules(policy, fwmark)?;
|
||||
+ } else {
|
||||
+ // skipping add_split_tunneling_rules as it won't cause traffic to leak
|
||||
+ log::warn!("net_cls mount not found, skipping add_split_tunneling_rules");
|
||||
+ }
|
||||
+
|
||||
self.add_dhcp_client_rules();
|
||||
self.add_ndp_rules();
|
||||
self.add_policy_specific_rules(policy, fwmark)?;
|
||||
@@ -311,6 +330,10 @@ impl<'a> PolicyBatch<'a> {
|
||||
Ok(self.batch.finalize())
|
||||
}
|
||||
|
||||
+ /// Allow split-tunneled traffic outside the tunnel.
|
||||
+ ///
|
||||
+ /// This is acheived by setting `fwmark` on connections initated by processes in the cgroup
|
||||
+ /// defined by [split_tunnel::NET_CLS_CLASSID].
|
||||
fn add_split_tunneling_rules(&mut self, policy: &FirewallPolicy, fwmark: u32) -> Result<()> {
|
||||
// Send select DNS requests in the tunnel
|
||||
if let FirewallPolicy::Connected {
|
|
@ -0,0 +1,167 @@
|
|||
From 69ebc3b8f16d1427c197b2951690bd1f4682362b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?David=20L=C3=B6nnhager?= <david.l@mullvad.net>
|
||||
Date: Mon, 9 Jun 2025 16:53:47 +0200
|
||||
Subject: [PATCH] Make daemon start without split tunneling
|
||||
|
||||
---
|
||||
mullvad-daemon/src/lib.rs | 2 +-
|
||||
talpid-core/src/split_tunnel/linux.rs | 64 +++++++++++++++++++--------
|
||||
2 files changed, 47 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
|
||||
index 79fbe1bb7cf0..edb770ec5809 100644
|
||||
--- a/mullvad-daemon/src/lib.rs
|
||||
+++ b/mullvad-daemon/src/lib.rs
|
||||
@@ -945,7 +945,7 @@ impl Daemon {
|
||||
},
|
||||
target_state,
|
||||
#[cfg(target_os = "linux")]
|
||||
- exclude_pids: split_tunnel::PidManager::new().map_err(Error::InitSplitTunneling)?,
|
||||
+ exclude_pids: split_tunnel::PidManager::default(),
|
||||
rx: internal_event_rx,
|
||||
tx: internal_event_tx,
|
||||
reconnection_job: None,
|
||||
diff --git a/talpid-core/src/split_tunnel/linux.rs b/talpid-core/src/split_tunnel/linux.rs
|
||||
index e96a0293defc..dfbf8fc479a4 100644
|
||||
--- a/talpid-core/src/split_tunnel/linux.rs
|
||||
+++ b/talpid-core/src/split_tunnel/linux.rs
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
io::{self, BufRead, BufReader, Write},
|
||||
- path::PathBuf,
|
||||
+ path::{Path, PathBuf},
|
||||
};
|
||||
use talpid_types::cgroup::{find_net_cls_mount, SPLIT_TUNNEL_CGROUP_NAME};
|
||||
|
||||
@@ -26,6 +26,10 @@ pub enum Error {
|
||||
#[error("Unable to create cgroup for excluded processes")]
|
||||
CreateCGroup(#[source] io::Error),
|
||||
|
||||
+ /// Split tunneling is unavailable
|
||||
+ #[error("Failed to set up split tunneling")]
|
||||
+ Unavailable,
|
||||
+
|
||||
/// Unable to set class ID for cgroup.
|
||||
#[error("Unable to set cgroup class ID")]
|
||||
SetCGroupClassId(#[source] io::Error),
|
||||
@@ -49,20 +53,36 @@ pub enum Error {
|
||||
|
||||
/// Manages PIDs in the Linux Cgroup excluded from the VPN tunnel.
|
||||
pub struct PidManager {
|
||||
- net_cls_path: PathBuf,
|
||||
+ inner: Inner,
|
||||
}
|
||||
|
||||
-impl PidManager {
|
||||
+enum Inner {
|
||||
+ Ok { net_cls_path: PathBuf },
|
||||
+ Failed { err: Error },
|
||||
+}
|
||||
+
|
||||
+impl Default for PidManager {
|
||||
/// Creates a new PID Cgroup manager.
|
||||
///
|
||||
/// Finds the corresponding Cgroup to use. Will mount a `net_cls` filesystem
|
||||
/// if none exists.
|
||||
- pub fn new() -> Result<PidManager, Error> {
|
||||
- let manager = PidManager {
|
||||
- net_cls_path: Self::create_cgroup()?,
|
||||
+ fn default() -> Self {
|
||||
+ let inner = match Self::new_inner() {
|
||||
+ Ok(net_cls_path) => Inner::Ok { net_cls_path },
|
||||
+ Err(err) => {
|
||||
+ log::error!("{}", err.display_chain_with_msg("Failed to enable split tunneling"));
|
||||
+ Inner::Failed { err }
|
||||
+ }
|
||||
};
|
||||
- manager.setup_exclusion_group()?;
|
||||
- Ok(manager)
|
||||
+ PidManager { inner }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl PidManager {
|
||||
+ fn new_inner() -> Result<PathBuf, Error> {
|
||||
+ let net_cls_path = Self::create_cgroup()?;
|
||||
+ Self::setup_exclusion_group(&net_cls_path)?;
|
||||
+ Ok(net_cls_path)
|
||||
}
|
||||
|
||||
/// Set up cgroup used to track PIDs for split tunneling.
|
||||
@@ -92,8 +112,8 @@ impl PidManager {
|
||||
Ok(net_cls_dir)
|
||||
}
|
||||
|
||||
- fn setup_exclusion_group(&self) -> Result<(), Error> {
|
||||
- let exclusions_dir = self.net_cls_path.join(SPLIT_TUNNEL_CGROUP_NAME);
|
||||
+ fn setup_exclusion_group(net_cls_path: &Path) -> Result<(), Error> {
|
||||
+ let exclusions_dir = net_cls_path.join(SPLIT_TUNNEL_CGROUP_NAME);
|
||||
if !exclusions_dir.exists() {
|
||||
fs::create_dir(exclusions_dir.clone()).map_err(Error::CreateCGroup)?;
|
||||
}
|
||||
@@ -103,10 +123,20 @@ impl PidManager {
|
||||
.map_err(Error::SetCGroupClassId)
|
||||
}
|
||||
|
||||
+ fn get_net_cls_path(&self) -> Result<&Path, Error> {
|
||||
+ match &self.inner {
|
||||
+ Inner::Ok { net_cls_path } => Ok(net_cls_path),
|
||||
+ Inner::Failed { err } => {
|
||||
+ log::error!("Failed to get netcls path: {err}");
|
||||
+ Err(Error::Unavailable)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/// Add a PID to the Cgroup to have it excluded from the tunnel.
|
||||
pub fn add(&self, pid: i32) -> Result<(), Error> {
|
||||
let exclusions_path = self
|
||||
- .net_cls_path
|
||||
+ .get_net_cls_path()?
|
||||
.join(SPLIT_TUNNEL_CGROUP_NAME)
|
||||
.join("cgroup.procs");
|
||||
|
||||
@@ -125,8 +155,7 @@ impl PidManager {
|
||||
pub fn remove(&self, pid: i32) -> Result<(), Error> {
|
||||
// FIXME: We remove PIDs from our cgroup here by adding
|
||||
// them to the parent cgroup. This seems wrong.
|
||||
- let mut file = self
|
||||
- .open_parent_cgroup_handle()
|
||||
+ let mut file = Self::open_parent_cgroup_handle(self.get_net_cls_path()?)
|
||||
.map_err(Error::RemoveCGroupPid)?;
|
||||
|
||||
file.write_all(pid.to_string().as_bytes())
|
||||
@@ -136,7 +165,7 @@ impl PidManager {
|
||||
/// Return a list of all PIDs currently in the Cgroup excluded from the tunnel.
|
||||
pub fn list(&self) -> Result<Vec<i32>, Error> {
|
||||
let exclusions_path = self
|
||||
- .net_cls_path
|
||||
+ .get_net_cls_path()?
|
||||
.join(SPLIT_TUNNEL_CGROUP_NAME)
|
||||
.join("cgroup.procs");
|
||||
|
||||
@@ -158,8 +187,7 @@ impl PidManager {
|
||||
pub fn clear(&self) -> Result<(), Error> {
|
||||
let pids = self.list()?;
|
||||
|
||||
- let mut file = self
|
||||
- .open_parent_cgroup_handle()
|
||||
+ let mut file = Self::open_parent_cgroup_handle(self.get_net_cls_path()?)
|
||||
.map_err(Error::RemoveCGroupPid)?;
|
||||
for pid in pids {
|
||||
file.write_all(pid.to_string().as_bytes())
|
||||
@@ -169,11 +197,11 @@ impl PidManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
- fn open_parent_cgroup_handle(&self) -> io::Result<fs::File> {
|
||||
+ fn open_parent_cgroup_handle(net_cls_path: &Path) -> io::Result<fs::File> {
|
||||
fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
- .open(self.net_cls_path.join("cgroup.procs"))
|
||||
+ .open(net_cls_path.join("cgroup.procs"))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
From a33f5961780f4d3831fc4a1ddf72b433e3fb4740 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?David=20L=C3=B6nnhager?= <david.l@mullvad.net>
|
||||
Date: Tue, 10 Jun 2025 09:36:48 +0200
|
||||
Subject: [PATCH] Add RPC for checking if split tunneling is available on Linux
|
||||
|
||||
---
|
||||
.../packages/mullvad-vpn/src/main/daemon-rpc.ts | 5 +++++
|
||||
mullvad-daemon/src/lib.rs | 11 +++++++++++
|
||||
mullvad-daemon/src/management_interface.rs | 15 +++++++++++++++
|
||||
.../proto/management_interface.proto | 1 +
|
||||
talpid-core/src/split_tunnel/linux.rs | 15 +++++++++++++--
|
||||
5 files changed, 45 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
|
||||
index d0d424418616..206070598c25 100644
|
||||
--- a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
|
||||
+++ b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
|
||||
@@ -510,6 +510,11 @@ export class DaemonRpc extends GrpcClient {
|
||||
await this.callBool(this.client.setSplitTunnelState, enabled);
|
||||
}
|
||||
|
||||
+ public async splitTunnelIsEnabled(): Promise<boolean> {
|
||||
+ const isEnabled = await this.callEmpty<BoolValue>(this.client.splitTunnelIsEnabled);
|
||||
+ return isEnabled.getValue();
|
||||
+ }
|
||||
+
|
||||
public async needFullDiskPermissions(): Promise<boolean> {
|
||||
const needFullDiskPermissions = await this.callEmpty<BoolValue>(
|
||||
this.client.needFullDiskPermissions,
|
||||
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
|
||||
index edb770ec5809..70db828aa3aa 100644
|
||||
--- a/mullvad-daemon/src/lib.rs
|
||||
+++ b/mullvad-daemon/src/lib.rs
|
||||
@@ -344,6 +344,9 @@ pub enum DaemonCommand {
|
||||
/// Remove settings and clear the cache
|
||||
#[cfg(not(target_os = "android"))]
|
||||
FactoryReset(ResponseTx<(), Error>),
|
||||
+ /// Return whether split tunneling is available
|
||||
+ #[cfg(target_os = "linux")]
|
||||
+ SplitTunnelIsEnabled(oneshot::Sender<bool>),
|
||||
/// Request list of processes excluded from the tunnel
|
||||
#[cfg(target_os = "linux")]
|
||||
GetSplitTunnelProcesses(ResponseTx<Vec<i32>, split_tunnel::Error>),
|
||||
@@ -1454,6 +1457,8 @@ impl Daemon {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
FactoryReset(tx) => self.on_factory_reset(tx).await,
|
||||
#[cfg(target_os = "linux")]
|
||||
+ SplitTunnelIsEnabled(tx) => self.on_split_tunnel_is_enabled(tx),
|
||||
+ #[cfg(target_os = "linux")]
|
||||
GetSplitTunnelProcesses(tx) => self.on_get_split_tunnel_processes(tx),
|
||||
#[cfg(target_os = "linux")]
|
||||
AddSplitTunnelProcess(tx, pid) => self.on_add_split_tunnel_process(tx, pid),
|
||||
@@ -2029,6 +2034,12 @@ impl Daemon {
|
||||
}));
|
||||
}
|
||||
|
||||
+ #[cfg(target_os = "linux")]
|
||||
+ fn on_split_tunnel_is_enabled(&mut self, tx: oneshot::Sender<bool>) {
|
||||
+ let enabled = self.exclude_pids.is_enabled();
|
||||
+ Self::oneshot_send(tx, enabled, "split_tunnel_is_enabled response");
|
||||
+ }
|
||||
+
|
||||
#[cfg(target_os = "linux")]
|
||||
fn on_get_split_tunnel_processes(&mut self, tx: ResponseTx<Vec<i32>, split_tunnel::Error>) {
|
||||
let result = self.exclude_pids.list().inspect_err(|error| {
|
||||
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
|
||||
index 7beadb3ddd38..5695683fcf15 100644
|
||||
--- a/mullvad-daemon/src/management_interface.rs
|
||||
+++ b/mullvad-daemon/src/management_interface.rs
|
||||
@@ -833,6 +833,21 @@ impl ManagementService for ManagementServiceImpl {
|
||||
// Split tunneling
|
||||
//
|
||||
|
||||
+ async fn split_tunnel_is_enabled(&self, _: Request<()>) -> ServiceResult<bool> {
|
||||
+ #[cfg(target_os = "linux")]
|
||||
+ {
|
||||
+ log::debug!("split_tunnel_is_enabled");
|
||||
+ let (tx, rx) = oneshot::channel();
|
||||
+ self.send_command_to_daemon(DaemonCommand::SplitTunnelIsEnabled(tx))?;
|
||||
+ Ok(self.wait_for_result(rx).await.map(Response::new)?)
|
||||
+ }
|
||||
+ #[cfg(not(target_os = "linux"))]
|
||||
+ {
|
||||
+ log::error!("split_tunnel_is_enabled is only available on Linux");
|
||||
+ Ok(Response::new(false))
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
async fn get_split_tunnel_processes(
|
||||
&self,
|
||||
_: Request<()>,
|
||||
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
|
||||
index f3be6708220e..ab9883c1efdd 100644
|
||||
--- a/mullvad-management-interface/proto/management_interface.proto
|
||||
+++ b/mullvad-management-interface/proto/management_interface.proto
|
||||
@@ -97,6 +97,7 @@ service ManagementService {
|
||||
rpc TestApiAccessMethodById(UUID) returns (google.protobuf.BoolValue) {}
|
||||
|
||||
// Split tunneling (Linux)
|
||||
+ rpc SplitTunnelIsEnabled(google.protobuf.Empty) returns (google.protobuf.BoolValue) {}
|
||||
rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {}
|
||||
rpc AddSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {}
|
||||
rpc RemoveSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {}
|
||||
diff --git a/talpid-core/src/split_tunnel/linux.rs b/talpid-core/src/split_tunnel/linux.rs
|
||||
index dfbf8fc479a4..1efb1dc60bf0 100644
|
||||
--- a/talpid-core/src/split_tunnel/linux.rs
|
||||
+++ b/talpid-core/src/split_tunnel/linux.rs
|
||||
@@ -3,7 +3,10 @@ use std::{
|
||||
io::{self, BufRead, BufReader, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
-use talpid_types::cgroup::{find_net_cls_mount, SPLIT_TUNNEL_CGROUP_NAME};
|
||||
+use talpid_types::{
|
||||
+ cgroup::{find_net_cls_mount, SPLIT_TUNNEL_CGROUP_NAME},
|
||||
+ ErrorExt,
|
||||
+};
|
||||
|
||||
const DEFAULT_NET_CLS_DIR: &str = "/sys/fs/cgroup/net_cls";
|
||||
const NET_CLS_DIR_OVERRIDE_ENV_VAR: &str = "TALPID_NET_CLS_MOUNT_DIR";
|
||||
@@ -70,7 +73,10 @@ impl Default for PidManager {
|
||||
let inner = match Self::new_inner() {
|
||||
Ok(net_cls_path) => Inner::Ok { net_cls_path },
|
||||
Err(err) => {
|
||||
- log::error!("{}", err.display_chain_with_msg("Failed to enable split tunneling"));
|
||||
+ log::error!(
|
||||
+ "{}",
|
||||
+ err.display_chain_with_msg("Failed to enable split tunneling")
|
||||
+ );
|
||||
Inner::Failed { err }
|
||||
}
|
||||
};
|
||||
@@ -197,6 +203,11 @@ impl PidManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+ /// Return whether it is enabled
|
||||
+ pub fn is_enabled(&self) -> bool {
|
||||
+ matches!(self.inner, Inner::Ok { .. })
|
||||
+ }
|
||||
+
|
||||
fn open_parent_cgroup_handle(net_cls_path: &Path) -> io::Result<fs::File> {
|
||||
fs::OpenOptions::new()
|
||||
.write(true)
|
Loading…
Add table
Reference in a new issue