summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2026-01-15 17:54:03 +0100
committerDavid Lönnhager <david.l@mullvad.net>2026-01-15 17:54:03 +0100
commit9370dc3f6243e9d6d1f6320314ae9b3b1553524c (patch)
tree32ea00c9d91821cac652f88c2c219519c82cc139
parent4e789aad1467a60ade2f17dd68d409d1d8ec928c (diff)
parentcf2726e1f5b1ba2b44ea0ea2f0f2c71ff8265cd3 (diff)
downloadmullvadvpn-9370dc3f6243e9d6d1f6320314ae9b3b1553524c.tar.xz
mullvadvpn-9370dc3f6243e9d6d1f6320314ae9b3b1553524c.zip
Merge branch 'temp-disable-cgroup2'
-rw-r--r--CHANGELOG.md2
-rw-r--r--mullvad-daemon/Cargo.toml1
-rw-r--r--mullvad-exclude/Cargo.toml3
-rw-r--r--mullvad-exclude/src/main.rs32
-rw-r--r--talpid-core/Cargo.toml1
-rw-r--r--talpid-core/src/firewall/linux.rs8
-rw-r--r--talpid-core/src/split_tunnel/linux/mod.rs55
7 files changed, 65 insertions, 37 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd3e891a55..7e2c63a3f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,8 +38,6 @@ Line wrap the file at 100 chars. Th
### Changed
#### Linux
-- Upgrade split-tunneling to use cgroups v2, instead of the deprecated cgroups v1.
- Users on Linux kernels prior to 5.13 will not be able to use split tunneling.
- Change "Go back" keyboard shortcut from `Esc` to `Alt + Left Arrow` or `Alt + [`.
#### macOS
diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml
index d04a2eb48b..8bdff598e3 100644
--- a/mullvad-daemon/Cargo.toml
+++ b/mullvad-daemon/Cargo.toml
@@ -16,6 +16,7 @@ api-override = ["mullvad-api/api-override"]
wireguard-go = ["talpid-core/wireguard-go"]
staggered-obfuscation = ["mullvad-relay-selector/staggered-obfuscation"]
multihop-pcap = ["talpid-core/multihop-pcap"]
+cgroup2 = ["talpid-core/cgroup2"]
[dependencies]
anyhow = { workspace = true }
diff --git a/mullvad-exclude/Cargo.toml b/mullvad-exclude/Cargo.toml
index 47070e1e1a..1a6368f413 100644
--- a/mullvad-exclude/Cargo.toml
+++ b/mullvad-exclude/Cargo.toml
@@ -7,6 +7,9 @@ license.workspace = true
edition.workspace = true
rust-version.workspace = true
+[features]
+cgroup2 = []
+
[lints]
workspace = true
diff --git a/mullvad-exclude/src/main.rs b/mullvad-exclude/src/main.rs
index c6afe1ab24..b2b5626f0a 100644
--- a/mullvad-exclude/src/main.rs
+++ b/mullvad-exclude/src/main.rs
@@ -14,7 +14,7 @@ mod inner {
fmt::Write as _,
os::unix::ffi::OsStrExt,
};
- use talpid_cgroup::{SPLIT_TUNNEL_CGROUP_NAME, find_net_cls_mount, v1::CGroup1, v2::CGroup2};
+ use talpid_cgroup::{SPLIT_TUNNEL_CGROUP_NAME, find_net_cls_mount, v1::CGroup1};
#[derive(thiserror::Error, Debug)]
enum Error {
@@ -94,9 +94,21 @@ mod inner {
.collect::<Result<Vec<CString>, NulError>>()
.map_err(Error::ArgumentNul)?;
- let pid = getpid();
+ exclude(getpid())?;
- let result = CGroup2::open_root()
+ // Drop root privileges
+ let real_uid = getuid();
+ setuid(real_uid).map_err(Error::DropRootUid)?;
+ let real_gid = getgid();
+ setgid(real_gid).map_err(Error::DropRootGid)?;
+
+ // Launch the process
+ execvp(&program, &args).map_err(Error::Exec)
+ }
+
+ #[cfg(feature = "cgroup2")]
+ fn exclude(pid: Pid) -> Result<(), Error> {
+ let result = talpid_cgroup::v2::CGroup2::open_root()
.and_then(|root_cgroup2| root_cgroup2.create_or_open_child(SPLIT_TUNNEL_CGROUP_NAME))
.and_then(|exclusion_cgroup2| exclusion_cgroup2.add_pid(pid));
@@ -108,15 +120,11 @@ mod inner {
eprintln!("Failed to add process to v1 cgroup: {add_err}");
}
- result?;
-
- // Drop root privileges
- let real_uid = getuid();
- setuid(real_uid).map_err(Error::DropRootUid)?;
- let real_gid = getgid();
- setgid(real_gid).map_err(Error::DropRootGid)?;
+ Ok(result?)
+ }
- // Launch the process
- execvp(&program, &args).map_err(Error::Exec)
+ #[cfg(not(feature = "cgroup2"))]
+ fn exclude(pid: Pid) -> Result<(), Error> {
+ add_to_cgroups_v1_if_exists(pid)
}
}
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 5b42e7858b..89bd1b10a3 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -13,6 +13,7 @@ workspace = true
[features]
wireguard-go = ["talpid-wireguard/wireguard-go"]
multihop-pcap = ["talpid-wireguard/multihop-pcap"]
+cgroup2 = []
[dependencies]
anyhow = { workspace = true }
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs
index adf921db49..6aff84ae5d 100644
--- a/talpid-core/src/firewall/linux.rs
+++ b/talpid-core/src/firewall/linux.rs
@@ -130,6 +130,10 @@ impl Firewall {
excluded_cgroup2: Option<CGroup2>,
net_cls: Option<u32>,
) -> Result<Self> {
+ if cfg!(not(feature = "cgroup2")) && excluded_cgroup2.is_some() {
+ log::error!("cgroup2 support disabled, but excluded_cgroup2 was provided");
+ }
+
Ok(Firewall {
fwmark,
excluded_cgroup2,
@@ -351,7 +355,9 @@ impl<'a> PolicyBatch<'a> {
policy: &FirewallPolicy,
firewall: &Firewall,
) -> Result<()> {
- if let Some(cgroup2) = &firewall.excluded_cgroup2 {
+ if cfg!(feature = "cgroup2")
+ && let Some(cgroup2) = &firewall.excluded_cgroup2
+ {
self.add_actual_split_tunneling_rules(policy, firewall.fwmark, |rule| {
// 1. From Linux kernel documentation:
// cgroup(2) is a mechanism to organize processes hierarchically ... cgroups form a tree structure and
diff --git a/talpid-core/src/split_tunnel/linux/mod.rs b/talpid-core/src/split_tunnel/linux/mod.rs
index dbab5e1b1d..79df04fae6 100644
--- a/talpid-core/src/split_tunnel/linux/mod.rs
+++ b/talpid-core/src/split_tunnel/linux/mod.rs
@@ -5,6 +5,7 @@
use anyhow::Context;
use libc::pid_t;
+#[cfg(feature = "cgroup2")]
use nftnl::{Batch, Chain, Hook, MsgType, Policy, ProtoFamily, Rule, Table, nft_expr};
use nix::unistd::Pid;
use talpid_cgroup::{
@@ -13,6 +14,7 @@ use talpid_cgroup::{
v2::CGroup2,
};
+#[cfg(feature = "cgroup2")]
use crate::firewall;
/// Value used to mark packets and associated connections.
@@ -39,6 +41,7 @@ pub struct PidManager {
enum Inner {
CGroup1(InnerCGroup1),
+ #[cfg(feature = "cgroup2")]
CGroup2(InnerCGroup2),
}
@@ -48,6 +51,7 @@ struct InnerCGroup1 {
net_cls_classid: u32,
}
+#[cfg(feature = "cgroup2")]
struct InnerCGroup2 {
root_cgroup2: CGroup2,
excluded_cgroup2: CGroup2,
@@ -65,33 +69,34 @@ impl PidManager {
}
fn new_inner() -> Result<Inner, Error> {
- // Try to create the cgroup2.
- let inner = match Self::new_cgroup2() {
- Ok(inner) => Inner::CGroup2(inner),
- Err(cgroup2_err) => {
- // If it does not success, the kernel might be too old, so we fallback on the old cgroup1 solution.
- match Self::new_cgroup1() {
- Ok(inner) => {
- log::warn!(
- "Failed to initialize cgroups v2, falling back to cgroup v1 for split tunneling"
- );
- log::warn!(
- "Note that cgroups v1 is deprecated and will be removed in the future"
- );
- Inner::CGroup1(inner)
- }
- Err(cgroup1_err) => {
+ #[cfg(feature = "cgroup2")]
+ return Self::new_cgroup2()
+ .map(Inner::CGroup2)
+ .or_else(|cgroup2_err| {
+ log::warn!(
+ "Failed to initialize cgroups v2, falling back to cgroup v1 for split tunneling"
+ );
+ log::warn!("Note that cgroups v1 is deprecated and will be removed in the future");
+
+ // If it does not succeed, the kernel might be too old, so we fallback on the old cgroup1 solution.
+ Self::new_cgroup1()
+ .map(Inner::CGroup1)
+ .map_err(|cgroup1_err| {
log::error!("Failed to initialize split-tunneling");
log::trace!("{cgroup1_err:?}");
log::trace!("{cgroup2_err:?}");
- return Err(cgroup2_err);
- }
- }
- }
- };
- Ok(inner)
+ cgroup2_err
+ })
+ });
+
+ #[cfg(not(feature = "cgroup2"))]
+ Self::new_cgroup1().map(Inner::CGroup1).inspect_err(|err| {
+ log::error!("Failed to initialize split-tunneling");
+ log::trace!("{err:?}");
+ })
}
+ #[cfg(feature = "cgroup2")]
fn new_cgroup2() -> Result<InnerCGroup2, Error> {
let root_cgroup2 = CGroup2::open_root()?;
@@ -185,6 +190,7 @@ impl Inner {
fn add(&self, pid: Pid) -> Result<(), Error> {
match self {
Inner::CGroup1(inner) => inner.excluded_cgroup1.add_pid(pid)?,
+ #[cfg(feature = "cgroup2")]
Inner::CGroup2(inner) => inner.excluded_cgroup2.add_pid(pid)?,
}
Ok(())
@@ -195,6 +201,7 @@ impl Inner {
// PIDs can only be removed from a cgroup by adding them to another cgroup.
match self {
Inner::CGroup1(inner) => inner.root_cgroup1.add_pid(pid)?,
+ #[cfg(feature = "cgroup2")]
Inner::CGroup2(inner) => inner.root_cgroup2.add_pid(pid)?,
}
Ok(())
@@ -204,6 +211,7 @@ impl Inner {
fn list(&mut self) -> Result<Vec<pid_t>, Error> {
Ok(match self {
Inner::CGroup1(inner) => inner.excluded_cgroup1.list_pids()?,
+ #[cfg(feature = "cgroup2")]
Inner::CGroup2(inner) => inner.excluded_cgroup2.list_pids()?,
})
}
@@ -227,6 +235,7 @@ impl Inner {
fn excluded_cgroup(&self) -> Result<Option<CGroup2>, Error> {
match self {
Inner::CGroup1(..) => Ok(None),
+ #[cfg(feature = "cgroup2")]
Inner::CGroup2(inner) => Ok(inner.excluded_cgroup2.try_clone().map(Some)?),
}
}
@@ -237,6 +246,7 @@ impl Inner {
fn net_cls_classid(&self) -> Option<u32> {
match self {
Inner::CGroup1(inner) => Some(inner.net_cls_classid),
+ #[cfg(feature = "cgroup2")]
Inner::CGroup2(..) => None,
}
}
@@ -252,6 +262,7 @@ impl Inner {
// Consider either having this module take ownership of setting up the split-tunneling nft rules,
// or moving this logic into the firewall module and coupling it with the actual firewall rules we
// set up.
+#[cfg(feature = "cgroup2")]
fn assert_nft_supports_cgroup2(cgroup: &CGroup2) -> Result<(), Error> {
let table_name = c"mullvad-test-cgroup2-capability";