diff options
| author | David Lönnhager <david.l@mullvad.net> | 2026-01-15 17:54:03 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2026-01-15 17:54:03 +0100 |
| commit | 9370dc3f6243e9d6d1f6320314ae9b3b1553524c (patch) | |
| tree | 32ea00c9d91821cac652f88c2c219519c82cc139 | |
| parent | 4e789aad1467a60ade2f17dd68d409d1d8ec928c (diff) | |
| parent | cf2726e1f5b1ba2b44ea0ea2f0f2c71ff8265cd3 (diff) | |
| download | mullvadvpn-9370dc3f6243e9d6d1f6320314ae9b3b1553524c.tar.xz mullvadvpn-9370dc3f6243e9d6d1f6320314ae9b3b1553524c.zip | |
Merge branch 'temp-disable-cgroup2'
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-exclude/Cargo.toml | 3 | ||||
| -rw-r--r-- | mullvad-exclude/src/main.rs | 32 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-core/src/firewall/linux.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/split_tunnel/linux/mod.rs | 55 |
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"; |
