diff options
| author | Emīls <emils@mullvad.net> | 2020-07-21 12:11:51 +0100 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2020-07-23 10:36:09 +0100 |
| commit | 6a6e5dc05ea8cfd775aa6fb57ebdd328ee1f1884 (patch) | |
| tree | 393c88b5a6372716e42faf8fcd29610c99a70603 | |
| parent | 05fcc36628b7fd47ce348c038e9f19d9587352b1 (diff) | |
| download | mullvadvpn-6a6e5dc05ea8cfd775aa6fb57ebdd328ee1f1884.tar.xz mullvadvpn-6a6e5dc05ea8cfd775aa6fb57ebdd328ee1f1884.zip | |
Determine `net_cls` mountpoint dynamically
| -rw-r--r-- | mullvad-exclude/src/main.rs | 22 | ||||
| -rw-r--r-- | talpid-core/src/split_tunnel/linux.rs | 67 | ||||
| -rw-r--r-- | talpid-types/src/cgroup.rs | 158 | ||||
| -rw-r--r-- | talpid-types/src/lib.rs | 2 |
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. |
