19d1f1594SRoman Gushchin // SPDX-License-Identifier: GPL-2.0
29d1f1594SRoman Gushchin #define _GNU_SOURCE
39d1f1594SRoman Gushchin #include <sched.h>
49d1f1594SRoman Gushchin #include <sys/mount.h>
59d1f1594SRoman Gushchin #include <sys/stat.h>
69d1f1594SRoman Gushchin #include <sys/types.h>
79d1f1594SRoman Gushchin #include <linux/limits.h>
89d1f1594SRoman Gushchin #include <stdio.h>
9f269099aSYonghong Song #include <stdlib.h>
109d1f1594SRoman Gushchin #include <linux/sched.h>
119d1f1594SRoman Gushchin #include <fcntl.h>
129d1f1594SRoman Gushchin #include <unistd.h>
139d1f1594SRoman Gushchin #include <ftw.h>
149d1f1594SRoman Gushchin
159d1f1594SRoman Gushchin #include "cgroup_helpers.h"
16b3c09fdcSRong Tao #include "bpf_util.h"
179d1f1594SRoman Gushchin
189d1f1594SRoman Gushchin /*
199d1f1594SRoman Gushchin * To avoid relying on the system setup, when setup_cgroup_env is called
20d8079d80SDaniel Borkmann * we create a new mount namespace, and cgroup namespace. The cgroupv2
21d8079d80SDaniel Borkmann * root is mounted at CGROUP_MOUNT_PATH. Unfortunately, most people don't
22d8079d80SDaniel Borkmann * have cgroupv2 enabled at this point in time. It's easier to create our
23d8079d80SDaniel Borkmann * own mount namespace and manage it ourselves. We assume /mnt exists.
249d1f1594SRoman Gushchin *
25d8079d80SDaniel Borkmann * Related cgroupv1 helpers are named *classid*(), since we only use the
26d8079d80SDaniel Borkmann * net_cls controller for tagging net_cls.classid. We assume the default
27d8079d80SDaniel Borkmann * mount under /sys/fs/cgroup/net_cls, which should be the case for the
28d8079d80SDaniel Borkmann * vast majority of users.
299d1f1594SRoman Gushchin */
309d1f1594SRoman Gushchin
319d1f1594SRoman Gushchin #define WALK_FD_LIMIT 16
32d8079d80SDaniel Borkmann
339d1f1594SRoman Gushchin #define CGROUP_MOUNT_PATH "/mnt"
34d8079d80SDaniel Borkmann #define CGROUP_MOUNT_DFLT "/sys/fs/cgroup"
35d8079d80SDaniel Borkmann #define NETCLS_MOUNT_PATH CGROUP_MOUNT_DFLT "/net_cls"
369d1f1594SRoman Gushchin #define CGROUP_WORK_DIR "/cgroup-test-work-dir"
37434992bbSYosry Ahmed
38434992bbSYosry Ahmed #define format_cgroup_path_pid(buf, path, pid) \
39e87c3434SYucong Sun snprintf(buf, sizeof(buf), "%s%s%d%s", CGROUP_MOUNT_PATH, \
40434992bbSYosry Ahmed CGROUP_WORK_DIR, pid, path)
41434992bbSYosry Ahmed
42434992bbSYosry Ahmed #define format_cgroup_path(buf, path) \
43434992bbSYosry Ahmed format_cgroup_path_pid(buf, path, getpid())
44434992bbSYosry Ahmed
45434992bbSYosry Ahmed #define format_parent_cgroup_path(buf, path) \
46434992bbSYosry Ahmed format_cgroup_path_pid(buf, path, getppid())
479d1f1594SRoman Gushchin
48d8079d80SDaniel Borkmann #define format_classid_path(buf) \
49d8079d80SDaniel Borkmann snprintf(buf, sizeof(buf), "%s%s", NETCLS_MOUNT_PATH, \
50d8079d80SDaniel Borkmann CGROUP_WORK_DIR)
51d8079d80SDaniel Borkmann
__enable_controllers(const char * cgroup_path,const char * controllers)52434992bbSYosry Ahmed static int __enable_controllers(const char *cgroup_path, const char *controllers)
53596092efSRoman Gushchin {
54596092efSRoman Gushchin char path[PATH_MAX + 1];
55434992bbSYosry Ahmed char enable[PATH_MAX + 1];
56596092efSRoman Gushchin char *c, *c2;
57596092efSRoman Gushchin int fd, cfd;
58cabd3e88SDan Carpenter ssize_t len;
59596092efSRoman Gushchin
60434992bbSYosry Ahmed /* If not controllers are passed, enable all available controllers */
61434992bbSYosry Ahmed if (!controllers) {
62434992bbSYosry Ahmed snprintf(path, sizeof(path), "%s/cgroup.controllers",
63434992bbSYosry Ahmed cgroup_path);
64596092efSRoman Gushchin fd = open(path, O_RDONLY);
65596092efSRoman Gushchin if (fd < 0) {
66596092efSRoman Gushchin log_err("Opening cgroup.controllers: %s", path);
67596092efSRoman Gushchin return 1;
68596092efSRoman Gushchin }
69434992bbSYosry Ahmed len = read(fd, enable, sizeof(enable) - 1);
70596092efSRoman Gushchin if (len < 0) {
71596092efSRoman Gushchin close(fd);
72596092efSRoman Gushchin log_err("Reading cgroup.controllers: %s", path);
73596092efSRoman Gushchin return 1;
74434992bbSYosry Ahmed } else if (len == 0) { /* No controllers to enable */
75596092efSRoman Gushchin close(fd);
76596092efSRoman Gushchin return 0;
77434992bbSYosry Ahmed }
78434992bbSYosry Ahmed enable[len] = 0;
79434992bbSYosry Ahmed close(fd);
80434992bbSYosry Ahmed } else {
81b3c09fdcSRong Tao bpf_strlcpy(enable, controllers, sizeof(enable));
82434992bbSYosry Ahmed }
83596092efSRoman Gushchin
84596092efSRoman Gushchin snprintf(path, sizeof(path), "%s/cgroup.subtree_control", cgroup_path);
85596092efSRoman Gushchin cfd = open(path, O_RDWR);
86596092efSRoman Gushchin if (cfd < 0) {
87596092efSRoman Gushchin log_err("Opening cgroup.subtree_control: %s", path);
88596092efSRoman Gushchin return 1;
89596092efSRoman Gushchin }
90596092efSRoman Gushchin
91434992bbSYosry Ahmed for (c = strtok_r(enable, " ", &c2); c; c = strtok_r(NULL, " ", &c2)) {
92596092efSRoman Gushchin if (dprintf(cfd, "+%s\n", c) <= 0) {
93596092efSRoman Gushchin log_err("Enabling controller %s: %s", c, path);
94596092efSRoman Gushchin close(cfd);
95596092efSRoman Gushchin return 1;
96596092efSRoman Gushchin }
97596092efSRoman Gushchin }
98596092efSRoman Gushchin close(cfd);
99596092efSRoman Gushchin return 0;
100596092efSRoman Gushchin }
101596092efSRoman Gushchin
102596092efSRoman Gushchin /**
103434992bbSYosry Ahmed * enable_controllers() - Enable cgroup v2 controllers
104434992bbSYosry Ahmed * @relative_path: The cgroup path, relative to the workdir
105434992bbSYosry Ahmed * @controllers: List of controllers to enable in cgroup.controllers format
106434992bbSYosry Ahmed *
107434992bbSYosry Ahmed *
108434992bbSYosry Ahmed * Enable given cgroup v2 controllers, if @controllers is NULL, enable all
109434992bbSYosry Ahmed * available controllers.
110434992bbSYosry Ahmed *
111434992bbSYosry Ahmed * If successful, 0 is returned.
112434992bbSYosry Ahmed */
enable_controllers(const char * relative_path,const char * controllers)113434992bbSYosry Ahmed int enable_controllers(const char *relative_path, const char *controllers)
114434992bbSYosry Ahmed {
115434992bbSYosry Ahmed char cgroup_path[PATH_MAX + 1];
116434992bbSYosry Ahmed
117434992bbSYosry Ahmed format_cgroup_path(cgroup_path, relative_path);
118434992bbSYosry Ahmed return __enable_controllers(cgroup_path, controllers);
119434992bbSYosry Ahmed }
120434992bbSYosry Ahmed
__write_cgroup_file(const char * cgroup_path,const char * file,const char * buf)121434992bbSYosry Ahmed static int __write_cgroup_file(const char *cgroup_path, const char *file,
122434992bbSYosry Ahmed const char *buf)
123434992bbSYosry Ahmed {
124434992bbSYosry Ahmed char file_path[PATH_MAX + 1];
125434992bbSYosry Ahmed int fd;
126434992bbSYosry Ahmed
127434992bbSYosry Ahmed snprintf(file_path, sizeof(file_path), "%s/%s", cgroup_path, file);
128434992bbSYosry Ahmed fd = open(file_path, O_RDWR);
129434992bbSYosry Ahmed if (fd < 0) {
130434992bbSYosry Ahmed log_err("Opening %s", file_path);
131434992bbSYosry Ahmed return 1;
132434992bbSYosry Ahmed }
133434992bbSYosry Ahmed
134434992bbSYosry Ahmed if (dprintf(fd, "%s", buf) <= 0) {
135434992bbSYosry Ahmed log_err("Writing to %s", file_path);
136434992bbSYosry Ahmed close(fd);
137434992bbSYosry Ahmed return 1;
138434992bbSYosry Ahmed }
139434992bbSYosry Ahmed close(fd);
140434992bbSYosry Ahmed return 0;
141434992bbSYosry Ahmed }
142434992bbSYosry Ahmed
143434992bbSYosry Ahmed /**
144434992bbSYosry Ahmed * write_cgroup_file() - Write to a cgroup file
145434992bbSYosry Ahmed * @relative_path: The cgroup path, relative to the workdir
146434992bbSYosry Ahmed * @file: The name of the file in cgroupfs to write to
147434992bbSYosry Ahmed * @buf: Buffer to write to the file
148434992bbSYosry Ahmed *
149434992bbSYosry Ahmed * Write to a file in the given cgroup's directory.
150434992bbSYosry Ahmed *
151434992bbSYosry Ahmed * If successful, 0 is returned.
152434992bbSYosry Ahmed */
write_cgroup_file(const char * relative_path,const char * file,const char * buf)153434992bbSYosry Ahmed int write_cgroup_file(const char *relative_path, const char *file,
154434992bbSYosry Ahmed const char *buf)
155434992bbSYosry Ahmed {
156434992bbSYosry Ahmed char cgroup_path[PATH_MAX - 24];
157434992bbSYosry Ahmed
158434992bbSYosry Ahmed format_cgroup_path(cgroup_path, relative_path);
159434992bbSYosry Ahmed return __write_cgroup_file(cgroup_path, file, buf);
160434992bbSYosry Ahmed }
161434992bbSYosry Ahmed
162434992bbSYosry Ahmed /**
163434992bbSYosry Ahmed * write_cgroup_file_parent() - Write to a cgroup file in the parent process
164434992bbSYosry Ahmed * workdir
165434992bbSYosry Ahmed * @relative_path: The cgroup path, relative to the parent process workdir
166434992bbSYosry Ahmed * @file: The name of the file in cgroupfs to write to
167434992bbSYosry Ahmed * @buf: Buffer to write to the file
168434992bbSYosry Ahmed *
169434992bbSYosry Ahmed * Write to a file in the given cgroup's directory under the parent process
170434992bbSYosry Ahmed * workdir.
171434992bbSYosry Ahmed *
172434992bbSYosry Ahmed * If successful, 0 is returned.
173434992bbSYosry Ahmed */
write_cgroup_file_parent(const char * relative_path,const char * file,const char * buf)174434992bbSYosry Ahmed int write_cgroup_file_parent(const char *relative_path, const char *file,
175434992bbSYosry Ahmed const char *buf)
176434992bbSYosry Ahmed {
177434992bbSYosry Ahmed char cgroup_path[PATH_MAX - 24];
178434992bbSYosry Ahmed
179434992bbSYosry Ahmed format_parent_cgroup_path(cgroup_path, relative_path);
180434992bbSYosry Ahmed return __write_cgroup_file(cgroup_path, file, buf);
181434992bbSYosry Ahmed }
182434992bbSYosry Ahmed
183434992bbSYosry Ahmed /**
1849d1f1594SRoman Gushchin * setup_cgroup_environment() - Setup the cgroup environment
1859d1f1594SRoman Gushchin *
1869d1f1594SRoman Gushchin * After calling this function, cleanup_cgroup_environment should be called
1879d1f1594SRoman Gushchin * once testing is complete.
1889d1f1594SRoman Gushchin *
1899d1f1594SRoman Gushchin * This function will print an error to stderr and return 1 if it is unable
1909d1f1594SRoman Gushchin * to setup the cgroup environment. If setup is successful, 0 is returned.
1919d1f1594SRoman Gushchin */
setup_cgroup_environment(void)1929d1f1594SRoman Gushchin int setup_cgroup_environment(void)
1939d1f1594SRoman Gushchin {
194c5881463SIvan Khoronzhuk char cgroup_workdir[PATH_MAX - 24];
1959d1f1594SRoman Gushchin
1969d1f1594SRoman Gushchin format_cgroup_path(cgroup_workdir, "");
1979d1f1594SRoman Gushchin
1989d1f1594SRoman Gushchin if (unshare(CLONE_NEWNS)) {
1999d1f1594SRoman Gushchin log_err("unshare");
2009d1f1594SRoman Gushchin return 1;
2019d1f1594SRoman Gushchin }
2029d1f1594SRoman Gushchin
2039d1f1594SRoman Gushchin if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
2049d1f1594SRoman Gushchin log_err("mount fakeroot");
2059d1f1594SRoman Gushchin return 1;
2069d1f1594SRoman Gushchin }
2079d1f1594SRoman Gushchin
2089d1f1594SRoman Gushchin if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) {
2099d1f1594SRoman Gushchin log_err("mount cgroup2");
2109d1f1594SRoman Gushchin return 1;
2119d1f1594SRoman Gushchin }
2129d1f1594SRoman Gushchin
2139d1f1594SRoman Gushchin /* Cleanup existing failed runs, now that the environment is setup */
2149d1f1594SRoman Gushchin cleanup_cgroup_environment();
2159d1f1594SRoman Gushchin
2169d1f1594SRoman Gushchin if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
2179d1f1594SRoman Gushchin log_err("mkdir cgroup work dir");
2189d1f1594SRoman Gushchin return 1;
2199d1f1594SRoman Gushchin }
2209d1f1594SRoman Gushchin
221434992bbSYosry Ahmed /* Enable all available controllers to increase test coverage */
222434992bbSYosry Ahmed if (__enable_controllers(CGROUP_MOUNT_PATH, NULL) ||
223434992bbSYosry Ahmed __enable_controllers(cgroup_workdir, NULL))
224596092efSRoman Gushchin return 1;
225596092efSRoman Gushchin
2269d1f1594SRoman Gushchin return 0;
2279d1f1594SRoman Gushchin }
2289d1f1594SRoman Gushchin
nftwfunc(const char * filename,const struct stat * statptr,int fileflags,struct FTW * pfwt)2299d1f1594SRoman Gushchin static int nftwfunc(const char *filename, const struct stat *statptr,
2309d1f1594SRoman Gushchin int fileflags, struct FTW *pfwt)
2319d1f1594SRoman Gushchin {
2329d1f1594SRoman Gushchin if ((fileflags & FTW_D) && rmdir(filename))
2339d1f1594SRoman Gushchin log_err("Removing cgroup: %s", filename);
2349d1f1594SRoman Gushchin return 0;
2359d1f1594SRoman Gushchin }
2369d1f1594SRoman Gushchin
join_cgroup_from_top(const char * cgroup_path)237d8079d80SDaniel Borkmann static int join_cgroup_from_top(const char *cgroup_path)
2389d1f1594SRoman Gushchin {
2399d1f1594SRoman Gushchin char cgroup_procs_path[PATH_MAX + 1];
2409d1f1594SRoman Gushchin pid_t pid = getpid();
2419d1f1594SRoman Gushchin int fd, rc = 0;
2429d1f1594SRoman Gushchin
2439d1f1594SRoman Gushchin snprintf(cgroup_procs_path, sizeof(cgroup_procs_path),
2449d1f1594SRoman Gushchin "%s/cgroup.procs", cgroup_path);
2459d1f1594SRoman Gushchin
2469d1f1594SRoman Gushchin fd = open(cgroup_procs_path, O_WRONLY);
2479d1f1594SRoman Gushchin if (fd < 0) {
2489d1f1594SRoman Gushchin log_err("Opening Cgroup Procs: %s", cgroup_procs_path);
2499d1f1594SRoman Gushchin return 1;
2509d1f1594SRoman Gushchin }
2519d1f1594SRoman Gushchin
2529d1f1594SRoman Gushchin if (dprintf(fd, "%d\n", pid) < 0) {
2539d1f1594SRoman Gushchin log_err("Joining Cgroup");
2549d1f1594SRoman Gushchin rc = 1;
2559d1f1594SRoman Gushchin }
2569d1f1594SRoman Gushchin
2579d1f1594SRoman Gushchin close(fd);
2589d1f1594SRoman Gushchin return rc;
2599d1f1594SRoman Gushchin }
2609d1f1594SRoman Gushchin
2619d1f1594SRoman Gushchin /**
2629d1f1594SRoman Gushchin * join_cgroup() - Join a cgroup
263434992bbSYosry Ahmed * @relative_path: The cgroup path, relative to the workdir, to join
2649d1f1594SRoman Gushchin *
2659d1f1594SRoman Gushchin * This function expects a cgroup to already be created, relative to the cgroup
2669d1f1594SRoman Gushchin * work dir, and it joins it. For example, passing "/my-cgroup" as the path
2679d1f1594SRoman Gushchin * would actually put the calling process into the cgroup
2689d1f1594SRoman Gushchin * "/cgroup-test-work-dir/my-cgroup"
2699d1f1594SRoman Gushchin *
2709d1f1594SRoman Gushchin * On success, it returns 0, otherwise on failure it returns 1.
2719d1f1594SRoman Gushchin */
join_cgroup(const char * relative_path)272434992bbSYosry Ahmed int join_cgroup(const char *relative_path)
2739d1f1594SRoman Gushchin {
2749d1f1594SRoman Gushchin char cgroup_path[PATH_MAX + 1];
2759d1f1594SRoman Gushchin
276434992bbSYosry Ahmed format_cgroup_path(cgroup_path, relative_path);
277434992bbSYosry Ahmed return join_cgroup_from_top(cgroup_path);
278434992bbSYosry Ahmed }
279434992bbSYosry Ahmed
280434992bbSYosry Ahmed /**
281539c7e67SKui-Feng Lee * join_root_cgroup() - Join the root cgroup
282539c7e67SKui-Feng Lee *
283539c7e67SKui-Feng Lee * This function joins the root cgroup.
284539c7e67SKui-Feng Lee *
285539c7e67SKui-Feng Lee * On success, it returns 0, otherwise on failure it returns 1.
286539c7e67SKui-Feng Lee */
join_root_cgroup(void)287539c7e67SKui-Feng Lee int join_root_cgroup(void)
288539c7e67SKui-Feng Lee {
289539c7e67SKui-Feng Lee return join_cgroup_from_top(CGROUP_MOUNT_PATH);
290539c7e67SKui-Feng Lee }
291539c7e67SKui-Feng Lee
292539c7e67SKui-Feng Lee /**
293434992bbSYosry Ahmed * join_parent_cgroup() - Join a cgroup in the parent process workdir
294434992bbSYosry Ahmed * @relative_path: The cgroup path, relative to parent process workdir, to join
295434992bbSYosry Ahmed *
296434992bbSYosry Ahmed * See join_cgroup().
297434992bbSYosry Ahmed *
298434992bbSYosry Ahmed * On success, it returns 0, otherwise on failure it returns 1.
299434992bbSYosry Ahmed */
join_parent_cgroup(const char * relative_path)300434992bbSYosry Ahmed int join_parent_cgroup(const char *relative_path)
301434992bbSYosry Ahmed {
302434992bbSYosry Ahmed char cgroup_path[PATH_MAX + 1];
303434992bbSYosry Ahmed
304434992bbSYosry Ahmed format_parent_cgroup_path(cgroup_path, relative_path);
3059d1f1594SRoman Gushchin return join_cgroup_from_top(cgroup_path);
3069d1f1594SRoman Gushchin }
3079d1f1594SRoman Gushchin
3089d1f1594SRoman Gushchin /**
3099d1f1594SRoman Gushchin * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
3109d1f1594SRoman Gushchin *
3119d1f1594SRoman Gushchin * This is an idempotent function to delete all temporary cgroups that
3129d1f1594SRoman Gushchin * have been created during the test, including the cgroup testing work
3139d1f1594SRoman Gushchin * directory.
3149d1f1594SRoman Gushchin *
3159d1f1594SRoman Gushchin * At call time, it moves the calling process to the root cgroup, and then
3169d1f1594SRoman Gushchin * runs the deletion process. It is idempotent, and should not fail, unless
3179d1f1594SRoman Gushchin * a process is lingering.
3189d1f1594SRoman Gushchin *
3199d1f1594SRoman Gushchin * On failure, it will print an error to stderr, and try to continue.
3209d1f1594SRoman Gushchin */
cleanup_cgroup_environment(void)3219d1f1594SRoman Gushchin void cleanup_cgroup_environment(void)
3229d1f1594SRoman Gushchin {
3239d1f1594SRoman Gushchin char cgroup_workdir[PATH_MAX + 1];
3249d1f1594SRoman Gushchin
3259d1f1594SRoman Gushchin format_cgroup_path(cgroup_workdir, "");
3269d1f1594SRoman Gushchin join_cgroup_from_top(CGROUP_MOUNT_PATH);
3279d1f1594SRoman Gushchin nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
3289d1f1594SRoman Gushchin }
3299d1f1594SRoman Gushchin
3309d1f1594SRoman Gushchin /**
331434992bbSYosry Ahmed * get_root_cgroup() - Get the FD of the root cgroup
332434992bbSYosry Ahmed *
333434992bbSYosry Ahmed * On success, it returns the file descriptor. On failure, it returns -1.
334434992bbSYosry Ahmed * If there is a failure, it prints the error to stderr.
335434992bbSYosry Ahmed */
get_root_cgroup(void)336434992bbSYosry Ahmed int get_root_cgroup(void)
337434992bbSYosry Ahmed {
338434992bbSYosry Ahmed int fd;
339434992bbSYosry Ahmed
340434992bbSYosry Ahmed fd = open(CGROUP_MOUNT_PATH, O_RDONLY);
341434992bbSYosry Ahmed if (fd < 0) {
342434992bbSYosry Ahmed log_err("Opening root cgroup");
343434992bbSYosry Ahmed return -1;
344434992bbSYosry Ahmed }
345434992bbSYosry Ahmed return fd;
346434992bbSYosry Ahmed }
347434992bbSYosry Ahmed
3482a42461aSHou Tao /*
3492a42461aSHou Tao * remove_cgroup() - Remove a cgroup
3502a42461aSHou Tao * @relative_path: The cgroup path, relative to the workdir, to remove
3512a42461aSHou Tao *
3522a42461aSHou Tao * This function expects a cgroup to already be created, relative to the cgroup
3532a42461aSHou Tao * work dir. It also expects the cgroup doesn't have any children or live
3542a42461aSHou Tao * processes and it removes the cgroup.
3552a42461aSHou Tao *
3562a42461aSHou Tao * On failure, it will print an error to stderr.
3572a42461aSHou Tao */
remove_cgroup(const char * relative_path)3582a42461aSHou Tao void remove_cgroup(const char *relative_path)
3592a42461aSHou Tao {
3602a42461aSHou Tao char cgroup_path[PATH_MAX + 1];
3612a42461aSHou Tao
3622a42461aSHou Tao format_cgroup_path(cgroup_path, relative_path);
3632a42461aSHou Tao if (rmdir(cgroup_path))
3642a42461aSHou Tao log_err("rmdiring cgroup %s .. %s", relative_path, cgroup_path);
3652a42461aSHou Tao }
3662a42461aSHou Tao
367434992bbSYosry Ahmed /**
3689d1f1594SRoman Gushchin * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
369434992bbSYosry Ahmed * @relative_path: The cgroup path, relative to the workdir, to join
3709d1f1594SRoman Gushchin *
3719d1f1594SRoman Gushchin * This function creates a cgroup under the top level workdir and returns the
3729d1f1594SRoman Gushchin * file descriptor. It is idempotent.
3739d1f1594SRoman Gushchin *
374a8911d6dSStanislav Fomichev * On success, it returns the file descriptor. On failure it returns -1.
3759d1f1594SRoman Gushchin * If there is a failure, it prints the error to stderr.
3769d1f1594SRoman Gushchin */
create_and_get_cgroup(const char * relative_path)377434992bbSYosry Ahmed int create_and_get_cgroup(const char *relative_path)
3789d1f1594SRoman Gushchin {
3799d1f1594SRoman Gushchin char cgroup_path[PATH_MAX + 1];
3809d1f1594SRoman Gushchin int fd;
3819d1f1594SRoman Gushchin
382434992bbSYosry Ahmed format_cgroup_path(cgroup_path, relative_path);
3839d1f1594SRoman Gushchin if (mkdir(cgroup_path, 0777) && errno != EEXIST) {
384434992bbSYosry Ahmed log_err("mkdiring cgroup %s .. %s", relative_path, cgroup_path);
385a8911d6dSStanislav Fomichev return -1;
3869d1f1594SRoman Gushchin }
3879d1f1594SRoman Gushchin
3889d1f1594SRoman Gushchin fd = open(cgroup_path, O_RDONLY);
3899d1f1594SRoman Gushchin if (fd < 0) {
3909d1f1594SRoman Gushchin log_err("Opening Cgroup");
391a8911d6dSStanislav Fomichev return -1;
3929d1f1594SRoman Gushchin }
3939d1f1594SRoman Gushchin
3949d1f1594SRoman Gushchin return fd;
3959d1f1594SRoman Gushchin }
396f269099aSYonghong Song
397f269099aSYonghong Song /**
398f269099aSYonghong Song * get_cgroup_id() - Get cgroup id for a particular cgroup path
399434992bbSYosry Ahmed * @relative_path: The cgroup path, relative to the workdir, to join
400f269099aSYonghong Song *
401f269099aSYonghong Song * On success, it returns the cgroup id. On failure it returns 0,
402f269099aSYonghong Song * which is an invalid cgroup id.
403f269099aSYonghong Song * If there is a failure, it prints the error to stderr.
404f269099aSYonghong Song */
get_cgroup_id(const char * relative_path)405434992bbSYosry Ahmed unsigned long long get_cgroup_id(const char *relative_path)
406f269099aSYonghong Song {
407f269099aSYonghong Song int dirfd, err, flags, mount_id, fhsize;
408f269099aSYonghong Song union {
409f269099aSYonghong Song unsigned long long cgid;
410f269099aSYonghong Song unsigned char raw_bytes[8];
411f269099aSYonghong Song } id;
412f269099aSYonghong Song char cgroup_workdir[PATH_MAX + 1];
413f269099aSYonghong Song struct file_handle *fhp, *fhp2;
414f269099aSYonghong Song unsigned long long ret = 0;
415f269099aSYonghong Song
416434992bbSYosry Ahmed format_cgroup_path(cgroup_workdir, relative_path);
417f269099aSYonghong Song
418f269099aSYonghong Song dirfd = AT_FDCWD;
419f269099aSYonghong Song flags = 0;
420f269099aSYonghong Song fhsize = sizeof(*fhp);
421f269099aSYonghong Song fhp = calloc(1, fhsize);
422f269099aSYonghong Song if (!fhp) {
423f269099aSYonghong Song log_err("calloc");
424f269099aSYonghong Song return 0;
425f269099aSYonghong Song }
426f269099aSYonghong Song err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags);
427f269099aSYonghong Song if (err >= 0 || fhp->handle_bytes != 8) {
428f269099aSYonghong Song log_err("name_to_handle_at");
429f269099aSYonghong Song goto free_mem;
430f269099aSYonghong Song }
431f269099aSYonghong Song
432f269099aSYonghong Song fhsize = sizeof(struct file_handle) + fhp->handle_bytes;
433f269099aSYonghong Song fhp2 = realloc(fhp, fhsize);
434f269099aSYonghong Song if (!fhp2) {
435f269099aSYonghong Song log_err("realloc");
436f269099aSYonghong Song goto free_mem;
437f269099aSYonghong Song }
438f269099aSYonghong Song err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags);
439f269099aSYonghong Song fhp = fhp2;
440f269099aSYonghong Song if (err < 0) {
441f269099aSYonghong Song log_err("name_to_handle_at");
442f269099aSYonghong Song goto free_mem;
443f269099aSYonghong Song }
444f269099aSYonghong Song
445f269099aSYonghong Song memcpy(id.raw_bytes, fhp->f_handle, 8);
446f269099aSYonghong Song ret = id.cgid;
447f269099aSYonghong Song
448f269099aSYonghong Song free_mem:
449f269099aSYonghong Song free(fhp);
450f269099aSYonghong Song return ret;
451f269099aSYonghong Song }
4524939b284SJohn Fastabend
cgroup_setup_and_join(const char * path)4534939b284SJohn Fastabend int cgroup_setup_and_join(const char *path) {
4544939b284SJohn Fastabend int cg_fd;
4554939b284SJohn Fastabend
4564939b284SJohn Fastabend if (setup_cgroup_environment()) {
4574939b284SJohn Fastabend fprintf(stderr, "Failed to setup cgroup environment\n");
4584939b284SJohn Fastabend return -EINVAL;
4594939b284SJohn Fastabend }
4604939b284SJohn Fastabend
4614939b284SJohn Fastabend cg_fd = create_and_get_cgroup(path);
4624939b284SJohn Fastabend if (cg_fd < 0) {
4634939b284SJohn Fastabend fprintf(stderr, "Failed to create test cgroup\n");
4644939b284SJohn Fastabend cleanup_cgroup_environment();
4654939b284SJohn Fastabend return cg_fd;
4664939b284SJohn Fastabend }
4674939b284SJohn Fastabend
4684939b284SJohn Fastabend if (join_cgroup(path)) {
4694939b284SJohn Fastabend fprintf(stderr, "Failed to join cgroup\n");
4704939b284SJohn Fastabend cleanup_cgroup_environment();
4714939b284SJohn Fastabend return -EINVAL;
4724939b284SJohn Fastabend }
4734939b284SJohn Fastabend return cg_fd;
4744939b284SJohn Fastabend }
475d8079d80SDaniel Borkmann
476d8079d80SDaniel Borkmann /**
477d8079d80SDaniel Borkmann * setup_classid_environment() - Setup the cgroupv1 net_cls environment
478d8079d80SDaniel Borkmann *
479d8079d80SDaniel Borkmann * After calling this function, cleanup_classid_environment should be called
480d8079d80SDaniel Borkmann * once testing is complete.
481d8079d80SDaniel Borkmann *
482d8079d80SDaniel Borkmann * This function will print an error to stderr and return 1 if it is unable
483d8079d80SDaniel Borkmann * to setup the cgroup environment. If setup is successful, 0 is returned.
484d8079d80SDaniel Borkmann */
setup_classid_environment(void)485d8079d80SDaniel Borkmann int setup_classid_environment(void)
486d8079d80SDaniel Borkmann {
487d8079d80SDaniel Borkmann char cgroup_workdir[PATH_MAX + 1];
488d8079d80SDaniel Borkmann
489d8079d80SDaniel Borkmann format_classid_path(cgroup_workdir);
490d8079d80SDaniel Borkmann
491d8079d80SDaniel Borkmann if (mount("tmpfs", CGROUP_MOUNT_DFLT, "tmpfs", 0, NULL) &&
492d8079d80SDaniel Borkmann errno != EBUSY) {
493d8079d80SDaniel Borkmann log_err("mount cgroup base");
494d8079d80SDaniel Borkmann return 1;
495d8079d80SDaniel Borkmann }
496d8079d80SDaniel Borkmann
497d8079d80SDaniel Borkmann if (mkdir(NETCLS_MOUNT_PATH, 0777) && errno != EEXIST) {
498d8079d80SDaniel Borkmann log_err("mkdir cgroup net_cls");
499d8079d80SDaniel Borkmann return 1;
500d8079d80SDaniel Borkmann }
501d8079d80SDaniel Borkmann
502*ae81c559SYafang Shao if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls")) {
503*ae81c559SYafang Shao if (errno != EBUSY) {
504d8079d80SDaniel Borkmann log_err("mount cgroup net_cls");
505d8079d80SDaniel Borkmann return 1;
506d8079d80SDaniel Borkmann }
507d8079d80SDaniel Borkmann
508*ae81c559SYafang Shao if (rmdir(NETCLS_MOUNT_PATH)) {
509*ae81c559SYafang Shao log_err("rmdir cgroup net_cls");
510*ae81c559SYafang Shao return 1;
511*ae81c559SYafang Shao }
512*ae81c559SYafang Shao if (umount(CGROUP_MOUNT_DFLT)) {
513*ae81c559SYafang Shao log_err("umount cgroup base");
514*ae81c559SYafang Shao return 1;
515*ae81c559SYafang Shao }
516*ae81c559SYafang Shao }
517*ae81c559SYafang Shao
518d8079d80SDaniel Borkmann cleanup_classid_environment();
519d8079d80SDaniel Borkmann
520d8079d80SDaniel Borkmann if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
521d8079d80SDaniel Borkmann log_err("mkdir cgroup work dir");
522d8079d80SDaniel Borkmann return 1;
523d8079d80SDaniel Borkmann }
524d8079d80SDaniel Borkmann
525d8079d80SDaniel Borkmann return 0;
526d8079d80SDaniel Borkmann }
527d8079d80SDaniel Borkmann
528d8079d80SDaniel Borkmann /**
529d8079d80SDaniel Borkmann * set_classid() - Set a cgroupv1 net_cls classid
530d8079d80SDaniel Borkmann * @id: the numeric classid
531d8079d80SDaniel Borkmann *
532d8079d80SDaniel Borkmann * Writes the passed classid into the cgroup work dir's net_cls.classid
533d8079d80SDaniel Borkmann * file in order to later on trigger socket tagging.
534d8079d80SDaniel Borkmann *
535d8079d80SDaniel Borkmann * On success, it returns 0, otherwise on failure it returns 1. If there
536d8079d80SDaniel Borkmann * is a failure, it prints the error to stderr.
537d8079d80SDaniel Borkmann */
set_classid(unsigned int id)538d8079d80SDaniel Borkmann int set_classid(unsigned int id)
539d8079d80SDaniel Borkmann {
540d8079d80SDaniel Borkmann char cgroup_workdir[PATH_MAX - 42];
541d8079d80SDaniel Borkmann char cgroup_classid_path[PATH_MAX + 1];
542d8079d80SDaniel Borkmann int fd, rc = 0;
543d8079d80SDaniel Borkmann
544d8079d80SDaniel Borkmann format_classid_path(cgroup_workdir);
545d8079d80SDaniel Borkmann snprintf(cgroup_classid_path, sizeof(cgroup_classid_path),
546d8079d80SDaniel Borkmann "%s/net_cls.classid", cgroup_workdir);
547d8079d80SDaniel Borkmann
548d8079d80SDaniel Borkmann fd = open(cgroup_classid_path, O_WRONLY);
549d8079d80SDaniel Borkmann if (fd < 0) {
550d8079d80SDaniel Borkmann log_err("Opening cgroup classid: %s", cgroup_classid_path);
551d8079d80SDaniel Borkmann return 1;
552d8079d80SDaniel Borkmann }
553d8079d80SDaniel Borkmann
554d8079d80SDaniel Borkmann if (dprintf(fd, "%u\n", id) < 0) {
555d8079d80SDaniel Borkmann log_err("Setting cgroup classid");
556d8079d80SDaniel Borkmann rc = 1;
557d8079d80SDaniel Borkmann }
558d8079d80SDaniel Borkmann
559d8079d80SDaniel Borkmann close(fd);
560d8079d80SDaniel Borkmann return rc;
561d8079d80SDaniel Borkmann }
562d8079d80SDaniel Borkmann
563d8079d80SDaniel Borkmann /**
564d8079d80SDaniel Borkmann * join_classid() - Join a cgroupv1 net_cls classid
565d8079d80SDaniel Borkmann *
566d8079d80SDaniel Borkmann * This function expects the cgroup work dir to be already created, as we
567d8079d80SDaniel Borkmann * join it here. This causes the process sockets to be tagged with the given
568d8079d80SDaniel Borkmann * net_cls classid.
569d8079d80SDaniel Borkmann *
570d8079d80SDaniel Borkmann * On success, it returns 0, otherwise on failure it returns 1.
571d8079d80SDaniel Borkmann */
join_classid(void)572d8079d80SDaniel Borkmann int join_classid(void)
573d8079d80SDaniel Borkmann {
574d8079d80SDaniel Borkmann char cgroup_workdir[PATH_MAX + 1];
575d8079d80SDaniel Borkmann
576d8079d80SDaniel Borkmann format_classid_path(cgroup_workdir);
577d8079d80SDaniel Borkmann return join_cgroup_from_top(cgroup_workdir);
578d8079d80SDaniel Borkmann }
579d8079d80SDaniel Borkmann
580d8079d80SDaniel Borkmann /**
581d8079d80SDaniel Borkmann * cleanup_classid_environment() - Cleanup the cgroupv1 net_cls environment
582d8079d80SDaniel Borkmann *
583d8079d80SDaniel Borkmann * At call time, it moves the calling process to the root cgroup, and then
584d8079d80SDaniel Borkmann * runs the deletion process.
585d8079d80SDaniel Borkmann *
586d8079d80SDaniel Borkmann * On failure, it will print an error to stderr, and try to continue.
587d8079d80SDaniel Borkmann */
cleanup_classid_environment(void)588d8079d80SDaniel Borkmann void cleanup_classid_environment(void)
589d8079d80SDaniel Borkmann {
590d8079d80SDaniel Borkmann char cgroup_workdir[PATH_MAX + 1];
591d8079d80SDaniel Borkmann
592d8079d80SDaniel Borkmann format_classid_path(cgroup_workdir);
593d8079d80SDaniel Borkmann join_cgroup_from_top(NETCLS_MOUNT_PATH);
594d8079d80SDaniel Borkmann nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
595d8079d80SDaniel Borkmann }
596