summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-exclude/src/main.rs22
-rw-r--r--talpid-core/src/split_tunnel/linux.rs67
-rw-r--r--talpid-types/src/cgroup.rs158
-rw-r--r--talpid-types/src/lib.rs2
4 files changed, 218 insertions, 31 deletions
diff --git a/mullvad-exclude/src/main.rs b/mullvad-exclude/src/main.rs
index 4aa8fea4dc..6378857270 100644
--- a/mullvad-exclude/src/main.rs
+++ b/mullvad-exclude/src/main.rs
@@ -8,14 +8,10 @@ use std::{
fs,
io::{self, BufWriter, Write},
os::unix::ffi::OsStrExt,
- path::Path,
};
#[cfg(target_os = "linux")]
-use talpid_types::SPLIT_TUNNEL_CGROUP_NAME;
-
-#[cfg(target_os = "linux")]
-const NETCLS_DIR: &str = "/sys/fs/cgroup/net_cls/";
+use talpid_types::cgroup::{find_net_cls_mount, SPLIT_TUNNEL_CGROUP_NAME};
#[cfg(target_os = "linux")]
const PROGRAM_NAME: &str = "mullvad-exclude";
@@ -41,6 +37,12 @@ enum Error {
#[error(display = "An argument contains interior nul bytes")]
ArgumentNulError(#[error(source)] NulError),
+
+ #[error(display = "Failed to find net_cls controller")]
+ FindNetClsController(#[error(source)] io::Error),
+
+ #[error(display = "No net_cls controller")]
+ NoNetClsController,
}
fn main() {
@@ -80,9 +82,13 @@ fn run() -> Result<void::Void, Error> {
.map_err(Error::ArgumentNulError)?;
let args: Vec<&CStr> = args.iter().map(|arg| &**arg).collect();
- // Set the cgroup of this process
- let cgroup_dir = Path::new(NETCLS_DIR).join(SPLIT_TUNNEL_CGROUP_NAME);
- let procs_path = cgroup_dir.join("cgroup.procs");
+ let cgroup_dir = find_net_cls_mount()
+ .map_err(Error::FindNetClsController)?
+ .ok_or(Error::NoNetClsController)?;
+
+ let procs_path = cgroup_dir
+ .join(SPLIT_TUNNEL_CGROUP_NAME)
+ .join("cgroup.procs");
let file = fs::OpenOptions::new()
.write(true)
diff --git a/talpid-core/src/split_tunnel/linux.rs b/talpid-core/src/split_tunnel/linux.rs
index 6359e05669..8d9da5ebca 100644
--- a/talpid-core/src/split_tunnel/linux.rs
+++ b/talpid-core/src/split_tunnel/linux.rs
@@ -1,11 +1,12 @@
use std::{
fs,
io::{self, BufRead, BufReader, BufWriter, Write},
- path::Path,
+ path::PathBuf,
};
-use talpid_types::SPLIT_TUNNEL_CGROUP_NAME;
+use talpid_types::cgroup::{find_net_cls_mount, SPLIT_TUNNEL_CGROUP_NAME};
-const NETCLS_DIR: &str = "/sys/fs/cgroup/net_cls/";
+const DEFAULT_NETCLS_DIR: &str = "/sys/fs/cgroup/net_cls";
+const NETCLS_DIR_OVERRIDE_ENV_VAR: &str = "TALPID_NETCLS_MOUNT_DIR";
/// Identifies packets coming from the cgroup.
/// This should be an arbitrary but unique integer.
@@ -41,37 +42,57 @@ pub enum Error {
/// Unable to read cgroup.procs.
#[error(display = "Unable to obtain PIDs from cgroup.procs")]
ListCGroupPids(#[error(source)] io::Error),
+
+ /// Unable to read /proc/mounts
+ #[error(display = "Failed to read /proc/mounts")]
+ ListMounts(#[error(source)] io::Error),
}
/// Manages PIDs to exclude from the tunnel.
-pub struct PidManager(());
+pub struct PidManager {
+ net_cls_path: PathBuf,
+}
impl PidManager {
/// Create object to manage split-tunnel PIDs.
pub fn new() -> Result<PidManager, Error> {
- Self::create_cgroup()?;
- Ok(PidManager(()))
+ let manager = PidManager {
+ net_cls_path: Self::create_cgroup()?,
+ };
+ manager.setup_exclusion_group()?;
+ Ok(manager)
}
/// Set up cgroup used to track PIDs for split tunneling.
- fn create_cgroup() -> Result<(), Error> {
- let netcls_dir = Path::new(NETCLS_DIR);
+ fn create_cgroup() -> Result<PathBuf, Error> {
+ if let Some(net_cls_path) = find_net_cls_mount().map_err(Error::ListMounts)? {
+ return Ok(net_cls_path);
+ }
+
+ let netcls_dir = std::env::var(NETCLS_DIR_OVERRIDE_ENV_VAR)
+ .map(PathBuf::from)
+ .unwrap_or(PathBuf::from(DEFAULT_NETCLS_DIR));
+
if !netcls_dir.exists() {
fs::create_dir(netcls_dir.clone()).map_err(Error::CreateCGroup)?;
-
- // https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt
- nix::mount::mount(
- Some("net_cls"),
- netcls_dir,
- Some("cgroup"),
- nix::mount::MsFlags::empty(),
- Some("net_cls"),
- )
- .map_err(Error::InitNetClsCGroup)?;
}
- let exclusions_dir = netcls_dir.join(SPLIT_TUNNEL_CGROUP_NAME);
+ // https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt
+ nix::mount::mount(
+ Some("net_cls"),
+ &netcls_dir,
+ Some("cgroup"),
+ nix::mount::MsFlags::empty(),
+ Some("net_cls"),
+ )
+ .map_err(Error::InitNetClsCGroup)?;
+
+
+ Ok(netcls_dir)
+ }
+ fn setup_exclusion_group(&self) -> Result<(), Error> {
+ let exclusions_dir = self.net_cls_path.join(SPLIT_TUNNEL_CGROUP_NAME);
if !exclusions_dir.exists() {
fs::create_dir(exclusions_dir.clone()).map_err(Error::CreateCGroup)?;
}
@@ -88,7 +109,8 @@ impl PidManager {
/// Add PIDs to exclude from the tunnel.
pub fn add_list<T: Into<i32> + ToString>(&self, pids: &[T]) -> Result<(), Error> {
- let exclusions_path = Path::new(NETCLS_DIR)
+ let exclusions_path = self
+ .net_cls_path
.join(SPLIT_TUNNEL_CGROUP_NAME)
.join("cgroup.procs");
@@ -113,7 +135,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 exclusions_path = Path::new(NETCLS_DIR).join("cgroup.procs");
+ let exclusions_path = self.net_cls_path.join("cgroup.procs");
let mut file = fs::OpenOptions::new()
.write(true)
@@ -127,7 +149,8 @@ impl PidManager {
/// Return a list of PIDs that are excluded from the tunnel.
pub fn list(&self) -> Result<Vec<i32>, Error> {
- let exclusions_path = Path::new(NETCLS_DIR)
+ let exclusions_path = self
+ .net_cls_path
.join(SPLIT_TUNNEL_CGROUP_NAME)
.join("cgroup.procs");
diff --git a/talpid-types/src/cgroup.rs b/talpid-types/src/cgroup.rs
new file mode 100644
index 0000000000..6a3e8ca4fc
--- /dev/null
+++ b/talpid-types/src/cgroup.rs
@@ -0,0 +1,158 @@
+use std::{ffi::OsStr, fs, os::unix::ffi::OsStrExt, path::PathBuf};
+
+pub const SPLIT_TUNNEL_CGROUP_NAME: &str = "mullvad-exclusions";
+
+/// Find the path of the cgroup v1 net_cls controller mount if it exists
+pub fn find_net_cls_mount() -> std::io::Result<Option<PathBuf>> {
+ let mounts = fs::read("/proc/mounts")?;
+ Ok(find_net_cls_mount_inner(&mounts))
+}
+
+fn find_net_cls_mount_inner(mounts: &[u8]) -> Option<PathBuf> {
+ mounts
+ .split(|byte| *byte == b'\n')
+ .find_map(parse_mount_line)
+}
+
+fn parse_mount_line(line: &[u8]) -> Option<PathBuf> {
+ // Each line contains multiple values seperated by space.
+ // `cgroup /sys/fs/cgroup/net_cls,net_prio cgroup
+ // rw,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0` Value meanings:
+ // 1. device type
+ // 2. mount path
+ // 3. filesystem type
+ // 4. mount options
+ // 5./6. legacy dummy values
+ let mut parts = line.split(|byte| *byte == b' ');
+ let _device_type = parts.next()?;
+ let mount_path = parts.next()?;
+ let filesystem_type = parts.next()?;
+ let mount_options = parts.next()?;
+ // The expected device type and fs type is "cgroup";
+ if filesystem_type != b"cgroup" {
+ return None;
+ }
+
+ if !mount_options
+ .split(|byte| *byte == b',')
+ .any(|key| key == b"net_cls")
+ {
+ return None;
+ }
+
+ Some(PathBuf::from(OsStr::from_bytes(mount_path)))
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_find_net_cls_path() {
+ let input = br#"sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
+proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
+udev /dev devtmpfs rw,nosuid,noexec,relatime,size=989436k,nr_inodes=247359,mode=755 0 0
+devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
+tmpfs /run tmpfs rw,nosuid,nodev,noexec,relatime,size=203520k,mode=755 0 0
+/dev/vda5 / ext4 rw,relatime,errors=remount-ro 0 0
+securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
+tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0
+tmpfs /run/lock tmpfs rw,nosuid,nodev,noexec,relatime,size=5120k 0 0
+tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,mode=755 0 0
+cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate 0 0
+cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,name=systemd 0 0
+pstore /sys/fs/pstore pstore rw,nosuid,nodev,noexec,relatime 0 0
+none /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0
+cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
+cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
+cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
+cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
+cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0
+cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
+cgroup /sys/fs/cgroup/rdma cgroup rw,nosuid,nodev,noexec,relatime,rdma 0 0
+cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0
+cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
+cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
+cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
+systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=28,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=14329 0 0
+hugetlbfs /dev/hugepages hugetlbfs rw,relatime,pagesize=2M 0 0
+mqueue /dev/mqueue mqueue rw,nosuid,nodev,noexec,relatime 0 0
+debugfs /sys/kernel/debug debugfs rw,nosuid,nodev,noexec,relatime 0 0
+tracefs /sys/kernel/tracing tracefs rw,nosuid,nodev,noexec,relatime 0 0
+fusectl /sys/fs/fuse/connections fusectl rw,nosuid,nodev,noexec,relatime 0 0
+configfs /sys/kernel/config configfs rw,nosuid,nodev,noexec,relatime 0 0
+/dev/loop1 /snap/gnome-3-34-1804/24 squashfs ro,nodev,relatime 0 0
+/dev/loop2 /snap/core18/1880 squashfs ro,nodev,relatime 0 0
+/dev/loop3 /snap/gtk-common-themes/1506 squashfs ro,nodev,relatime 0 0
+/dev/loop0 /snap/core18/1754 squashfs ro,nodev,relatime 0 0
+/dev/loop5 /snap/snap-store/467 squashfs ro,nodev,relatime 0 0
+/dev/loop6 /snap/gnome-3-34-1804/36 squashfs ro,nodev,relatime 0 0
+/dev/loop7 /snap/snap-store/454 squashfs ro,nodev,relatime 0 0
+/dev/loop8 /snap/snapd/8140 squashfs ro,nodev,relatime 0 0
+/dev/vda1 /boot/efi vfat rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 0
+tmpfs /run/user/125 tmpfs rw,nosuid,nodev,relatime,size=203516k,mode=700,uid=125,gid=130 0 0
+gvfsd-fuse /run/user/125/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=125,group_id=130 0 0
+/dev/loop9 /snap/snapd/8542 squashfs ro,nodev,relatime 0 0
+tmpfs /run/user/1000 tmpfs rw,nosuid,nodev,relatime,size=203516k,mode=700,uid=1000,gid=1000 0 0
+gvfsd-fuse /run/user/1000/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+some-garbage-line
+"#;
+
+ assert_eq!(
+ find_net_cls_mount_inner(input),
+ Some(PathBuf::from("/sys/fs/cgroup/net_cls,net_prio"))
+ )
+ }
+
+ #[test]
+ fn test_fail_to_find_net_cls_path() {
+ let input = br#"sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
+proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
+udev /dev devtmpfs rw,nosuid,noexec,relatime,size=989436k,nr_inodes=247359,mode=755 0 0
+devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
+tmpfs /run tmpfs rw,nosuid,nodev,noexec,relatime,size=203520k,mode=755 0 0
+/dev/vda5 / ext4 rw,relatime,errors=remount-ro 0 0
+securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
+tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0
+tmpfs /run/lock tmpfs rw,nosuid,nodev,noexec,relatime,size=5120k 0 0
+tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,mode=755 0 0
+cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate 0 0
+cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,name=systemd 0 0
+pstore /sys/fs/pstore pstore rw,nosuid,nodev,noexec,relatime 0 0
+none /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0
+cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
+cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
+cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
+cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
+cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0
+cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
+cgroup /sys/fs/cgroup/rdma cgroup rw,nosuid,nodev,noexec,relatime,rdma 0 0
+cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
+cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
+cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
+systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=28,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=14329 0 0
+hugetlbfs /dev/hugepages hugetlbfs rw,relatime,pagesize=2M 0 0
+mqueue /dev/mqueue mqueue rw,nosuid,nodev,noexec,relatime 0 0
+debugfs /sys/kernel/debug debugfs rw,nosuid,nodev,noexec,relatime 0 0
+tracefs /sys/kernel/tracing tracefs rw,nosuid,nodev,noexec,relatime 0 0
+fusectl /sys/fs/fuse/connections fusectl rw,nosuid,nodev,noexec,relatime 0 0
+configfs /sys/kernel/config configfs rw,nosuid,nodev,noexec,relatime 0 0
+/dev/loop1 /snap/gnome-3-34-1804/24 squashfs ro,nodev,relatime 0 0
+/dev/loop2 /snap/core18/1880 squashfs ro,nodev,relatime 0 0
+/dev/loop3 /snap/gtk-common-themes/1506 squashfs ro,nodev,relatime 0 0
+/dev/loop0 /snap/core18/1754 squashfs ro,nodev,relatime 0 0
+/dev/loop5 /snap/snap-store/467 squashfs ro,nodev,relatime 0 0
+/dev/loop6 /snap/gnome-3-34-1804/36 squashfs ro,nodev,relatime 0 0
+/dev/loop7 /snap/snap-store/454 squashfs ro,nodev,relatime 0 0
+/dev/loop8 /snap/snapd/8140 squashfs ro,nodev,relatime 0 0
+/dev/vda1 /boot/efi vfat rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 0
+tmpfs /run/user/125 tmpfs rw,nosuid,nodev,relatime,size=203516k,mode=700,uid=125,gid=130 0 0
+gvfsd-fuse /run/user/125/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=125,group_id=130 0 0
+/dev/loop9 /snap/snapd/8542 squashfs ro,nodev,relatime 0 0
+tmpfs /run/user/1000 tmpfs rw,nosuid,nodev,relatime,size=203516k,mode=700,uid=1000,gid=1000 0 0
+gvfsd-fuse /run/user/1000/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+ "#;
+
+ assert_eq!(find_net_cls_mount_inner(input), None)
+ }
+}
diff --git a/talpid-types/src/lib.rs b/talpid-types/src/lib.rs
index 2ee772a756..8135a88fe0 100644
--- a/talpid-types/src/lib.rs
+++ b/talpid-types/src/lib.rs
@@ -8,7 +8,7 @@ pub mod net;
pub mod tunnel;
#[cfg(target_os = "linux")]
-pub const SPLIT_TUNNEL_CGROUP_NAME: &str = "mullvad-exclusions";
+pub mod cgroup;
/// Used to generate string representations of error chains.