1 // SPDX-License-Identifier: GPL-2.0
7 #include <linux/limits.h>
10 #include <linux/sched.h>
15 #include "cgroup_helpers.h"
19 * To avoid relying on the system setup, when setup_cgroup_env is called
20 * we create a new mount namespace, and cgroup namespace. The cgroupv2
21 * root is mounted at CGROUP_MOUNT_PATH. Unfortunately, most people don't
22 * have cgroupv2 enabled at this point in time. It's easier to create our
23 * own mount namespace and manage it ourselves. We assume /mnt exists.
25 * Related cgroupv1 helpers are named *classid*(), since we only use the
26 * net_cls controller for tagging net_cls.classid. We assume the default
27 * mount under /sys/fs/cgroup/net_cls, which should be the case for the
28 * vast majority of users.
31 #define WALK_FD_LIMIT 16
33 #define CGROUP_MOUNT_PATH "/mnt"
34 #define CGROUP_MOUNT_DFLT "/sys/fs/cgroup"
35 #define NETCLS_MOUNT_PATH CGROUP_MOUNT_DFLT "/net_cls"
36 #define CGROUP_WORK_DIR "/cgroup-test-work-dir"
38 #define format_cgroup_path_pid(buf, path, pid) \
39 snprintf(buf, sizeof(buf), "%s%s%d%s", CGROUP_MOUNT_PATH, \
40 CGROUP_WORK_DIR, pid, path)
42 #define format_cgroup_path(buf, path) \
43 format_cgroup_path_pid(buf, path, getpid())
45 #define format_parent_cgroup_path(buf, path) \
46 format_cgroup_path_pid(buf, path, getppid())
48 #define format_classid_path_pid(buf, pid) \
49 snprintf(buf, sizeof(buf), "%s%s%d", NETCLS_MOUNT_PATH, \
52 #define format_classid_path(buf) \
53 format_classid_path_pid(buf, getpid())
55 static __thread
bool cgroup_workdir_mounted
;
57 static void __cleanup_cgroup_environment(void);
59 static int __enable_controllers(const char *cgroup_path
, const char *controllers
)
61 char path
[PATH_MAX
+ 1];
62 char enable
[PATH_MAX
+ 1];
67 /* If not controllers are passed, enable all available controllers */
69 snprintf(path
, sizeof(path
), "%s/cgroup.controllers",
71 fd
= open(path
, O_RDONLY
);
73 log_err("Opening cgroup.controllers: %s", path
);
76 len
= read(fd
, enable
, sizeof(enable
) - 1);
79 log_err("Reading cgroup.controllers: %s", path
);
81 } else if (len
== 0) { /* No controllers to enable */
88 bpf_strlcpy(enable
, controllers
, sizeof(enable
));
91 snprintf(path
, sizeof(path
), "%s/cgroup.subtree_control", cgroup_path
);
92 cfd
= open(path
, O_RDWR
);
94 log_err("Opening cgroup.subtree_control: %s", path
);
98 for (c
= strtok_r(enable
, " ", &c2
); c
; c
= strtok_r(NULL
, " ", &c2
)) {
99 if (dprintf(cfd
, "+%s\n", c
) <= 0) {
100 log_err("Enabling controller %s: %s", c
, path
);
110 * enable_controllers() - Enable cgroup v2 controllers
111 * @relative_path: The cgroup path, relative to the workdir
112 * @controllers: List of controllers to enable in cgroup.controllers format
115 * Enable given cgroup v2 controllers, if @controllers is NULL, enable all
116 * available controllers.
118 * If successful, 0 is returned.
120 int enable_controllers(const char *relative_path
, const char *controllers
)
122 char cgroup_path
[PATH_MAX
+ 1];
124 format_cgroup_path(cgroup_path
, relative_path
);
125 return __enable_controllers(cgroup_path
, controllers
);
128 static int __write_cgroup_file(const char *cgroup_path
, const char *file
,
131 char file_path
[PATH_MAX
+ 1];
134 snprintf(file_path
, sizeof(file_path
), "%s/%s", cgroup_path
, file
);
135 fd
= open(file_path
, O_RDWR
);
137 log_err("Opening %s", file_path
);
141 if (dprintf(fd
, "%s", buf
) <= 0) {
142 log_err("Writing to %s", file_path
);
151 * write_cgroup_file() - Write to a cgroup file
152 * @relative_path: The cgroup path, relative to the workdir
153 * @file: The name of the file in cgroupfs to write to
154 * @buf: Buffer to write to the file
156 * Write to a file in the given cgroup's directory.
158 * If successful, 0 is returned.
160 int write_cgroup_file(const char *relative_path
, const char *file
,
163 char cgroup_path
[PATH_MAX
- 24];
165 format_cgroup_path(cgroup_path
, relative_path
);
166 return __write_cgroup_file(cgroup_path
, file
, buf
);
170 * write_cgroup_file_parent() - Write to a cgroup file in the parent process
172 * @relative_path: The cgroup path, relative to the parent process workdir
173 * @file: The name of the file in cgroupfs to write to
174 * @buf: Buffer to write to the file
176 * Write to a file in the given cgroup's directory under the parent process
179 * If successful, 0 is returned.
181 int write_cgroup_file_parent(const char *relative_path
, const char *file
,
184 char cgroup_path
[PATH_MAX
- 24];
186 format_parent_cgroup_path(cgroup_path
, relative_path
);
187 return __write_cgroup_file(cgroup_path
, file
, buf
);
191 * setup_cgroup_environment() - Setup the cgroup environment
193 * After calling this function, cleanup_cgroup_environment should be called
194 * once testing is complete.
196 * This function will print an error to stderr and return 1 if it is unable
197 * to setup the cgroup environment. If setup is successful, 0 is returned.
199 int setup_cgroup_environment(void)
201 char cgroup_workdir
[PATH_MAX
- 24];
203 format_cgroup_path(cgroup_workdir
, "");
205 if (mkdir(CGROUP_MOUNT_PATH
, 0777) && errno
!= EEXIST
) {
206 log_err("mkdir mount");
210 if (unshare(CLONE_NEWNS
)) {
215 if (mount("none", "/", NULL
, MS_REC
| MS_PRIVATE
, NULL
)) {
216 log_err("mount fakeroot");
220 if (mount("none", CGROUP_MOUNT_PATH
, "cgroup2", 0, NULL
) && errno
!= EBUSY
) {
221 log_err("mount cgroup2");
224 cgroup_workdir_mounted
= true;
226 /* Cleanup existing failed runs, now that the environment is setup */
227 __cleanup_cgroup_environment();
229 if (mkdir(cgroup_workdir
, 0777) && errno
!= EEXIST
) {
230 log_err("mkdir cgroup work dir");
234 /* Enable all available controllers to increase test coverage */
235 if (__enable_controllers(CGROUP_MOUNT_PATH
, NULL
) ||
236 __enable_controllers(cgroup_workdir
, NULL
))
242 static int nftwfunc(const char *filename
, const struct stat
*statptr
,
243 int fileflags
, struct FTW
*pfwt
)
245 if ((fileflags
& FTW_D
) && rmdir(filename
))
246 log_err("Removing cgroup: %s", filename
);
250 static int join_cgroup_from_top(const char *cgroup_path
)
252 char cgroup_procs_path
[PATH_MAX
+ 1];
253 pid_t pid
= getpid();
256 snprintf(cgroup_procs_path
, sizeof(cgroup_procs_path
),
257 "%s/cgroup.procs", cgroup_path
);
259 fd
= open(cgroup_procs_path
, O_WRONLY
);
261 log_err("Opening Cgroup Procs: %s", cgroup_procs_path
);
265 if (dprintf(fd
, "%d\n", pid
) < 0) {
266 log_err("Joining Cgroup");
275 * join_cgroup() - Join a cgroup
276 * @relative_path: The cgroup path, relative to the workdir, to join
278 * This function expects a cgroup to already be created, relative to the cgroup
279 * work dir, and it joins it. For example, passing "/my-cgroup" as the path
280 * would actually put the calling process into the cgroup
281 * "/cgroup-test-work-dir/my-cgroup"
283 * On success, it returns 0, otherwise on failure it returns 1.
285 int join_cgroup(const char *relative_path
)
287 char cgroup_path
[PATH_MAX
+ 1];
289 format_cgroup_path(cgroup_path
, relative_path
);
290 return join_cgroup_from_top(cgroup_path
);
294 * join_root_cgroup() - Join the root cgroup
296 * This function joins the root cgroup.
298 * On success, it returns 0, otherwise on failure it returns 1.
300 int join_root_cgroup(void)
302 return join_cgroup_from_top(CGROUP_MOUNT_PATH
);
306 * join_parent_cgroup() - Join a cgroup in the parent process workdir
307 * @relative_path: The cgroup path, relative to parent process workdir, to join
311 * On success, it returns 0, otherwise on failure it returns 1.
313 int join_parent_cgroup(const char *relative_path
)
315 char cgroup_path
[PATH_MAX
+ 1];
317 format_parent_cgroup_path(cgroup_path
, relative_path
);
318 return join_cgroup_from_top(cgroup_path
);
322 * __cleanup_cgroup_environment() - Delete temporary cgroups
324 * This is a helper for cleanup_cgroup_environment() that is responsible for
325 * deletion of all temporary cgroups that have been created during the test.
327 static void __cleanup_cgroup_environment(void)
329 char cgroup_workdir
[PATH_MAX
+ 1];
331 format_cgroup_path(cgroup_workdir
, "");
332 join_cgroup_from_top(CGROUP_MOUNT_PATH
);
333 nftw(cgroup_workdir
, nftwfunc
, WALK_FD_LIMIT
, FTW_DEPTH
| FTW_MOUNT
);
337 * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
339 * This is an idempotent function to delete all temporary cgroups that
340 * have been created during the test and unmount the cgroup testing work
343 * At call time, it moves the calling process to the root cgroup, and then
344 * runs the deletion process. It is idempotent, and should not fail, unless
345 * a process is lingering.
347 * On failure, it will print an error to stderr, and try to continue.
349 void cleanup_cgroup_environment(void)
351 __cleanup_cgroup_environment();
352 if (cgroup_workdir_mounted
&& umount(CGROUP_MOUNT_PATH
))
353 log_err("umount cgroup2");
354 cgroup_workdir_mounted
= false;
358 * get_root_cgroup() - Get the FD of the root cgroup
360 * On success, it returns the file descriptor. On failure, it returns -1.
361 * If there is a failure, it prints the error to stderr.
363 int get_root_cgroup(void)
367 fd
= open(CGROUP_MOUNT_PATH
, O_RDONLY
);
369 log_err("Opening root cgroup");
376 * remove_cgroup() - Remove a cgroup
377 * @relative_path: The cgroup path, relative to the workdir, to remove
379 * This function expects a cgroup to already be created, relative to the cgroup
380 * work dir. It also expects the cgroup doesn't have any children or live
381 * processes and it removes the cgroup.
383 * On failure, it will print an error to stderr.
385 void remove_cgroup(const char *relative_path
)
387 char cgroup_path
[PATH_MAX
+ 1];
389 format_cgroup_path(cgroup_path
, relative_path
);
390 if (rmdir(cgroup_path
))
391 log_err("rmdiring cgroup %s .. %s", relative_path
, cgroup_path
);
395 * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
396 * @relative_path: The cgroup path, relative to the workdir, to join
398 * This function creates a cgroup under the top level workdir and returns the
399 * file descriptor. It is idempotent.
401 * On success, it returns the file descriptor. On failure it returns -1.
402 * If there is a failure, it prints the error to stderr.
404 int create_and_get_cgroup(const char *relative_path
)
406 char cgroup_path
[PATH_MAX
+ 1];
409 format_cgroup_path(cgroup_path
, relative_path
);
410 if (mkdir(cgroup_path
, 0777) && errno
!= EEXIST
) {
411 log_err("mkdiring cgroup %s .. %s", relative_path
, cgroup_path
);
415 fd
= open(cgroup_path
, O_RDONLY
);
417 log_err("Opening Cgroup");
425 * get_cgroup_id_from_path - Get cgroup id for a particular cgroup path
426 * @cgroup_workdir: The absolute cgroup path
428 * On success, it returns the cgroup id. On failure it returns 0,
429 * which is an invalid cgroup id.
430 * If there is a failure, it prints the error to stderr.
432 static unsigned long long get_cgroup_id_from_path(const char *cgroup_workdir
)
434 int dirfd
, err
, flags
, mount_id
, fhsize
;
436 unsigned long long cgid
;
437 unsigned char raw_bytes
[8];
439 struct file_handle
*fhp
, *fhp2
;
440 unsigned long long ret
= 0;
444 fhsize
= sizeof(*fhp
);
445 fhp
= calloc(1, fhsize
);
450 err
= name_to_handle_at(dirfd
, cgroup_workdir
, fhp
, &mount_id
, flags
);
451 if (err
>= 0 || fhp
->handle_bytes
!= 8) {
452 log_err("name_to_handle_at");
456 fhsize
= sizeof(struct file_handle
) + fhp
->handle_bytes
;
457 fhp2
= realloc(fhp
, fhsize
);
462 err
= name_to_handle_at(dirfd
, cgroup_workdir
, fhp2
, &mount_id
, flags
);
465 log_err("name_to_handle_at");
469 memcpy(id
.raw_bytes
, fhp
->f_handle
, 8);
477 unsigned long long get_cgroup_id(const char *relative_path
)
479 char cgroup_workdir
[PATH_MAX
+ 1];
481 format_cgroup_path(cgroup_workdir
, relative_path
);
482 return get_cgroup_id_from_path(cgroup_workdir
);
485 int cgroup_setup_and_join(const char *path
) {
488 if (setup_cgroup_environment()) {
489 fprintf(stderr
, "Failed to setup cgroup environment\n");
493 cg_fd
= create_and_get_cgroup(path
);
495 fprintf(stderr
, "Failed to create test cgroup\n");
496 cleanup_cgroup_environment();
500 if (join_cgroup(path
)) {
501 fprintf(stderr
, "Failed to join cgroup\n");
502 cleanup_cgroup_environment();
509 * setup_classid_environment() - Setup the cgroupv1 net_cls environment
511 * This function should only be called in a custom mount namespace, e.g.
512 * created by running setup_cgroup_environment.
514 * After calling this function, cleanup_classid_environment should be called
515 * once testing is complete.
517 * This function will print an error to stderr and return 1 if it is unable
518 * to setup the cgroup environment. If setup is successful, 0 is returned.
520 int setup_classid_environment(void)
522 char cgroup_workdir
[PATH_MAX
+ 1];
524 format_classid_path(cgroup_workdir
);
526 if (mount("tmpfs", CGROUP_MOUNT_DFLT
, "tmpfs", 0, NULL
) &&
528 log_err("mount cgroup base");
532 if (mkdir(NETCLS_MOUNT_PATH
, 0777) && errno
!= EEXIST
) {
533 log_err("mkdir cgroup net_cls");
537 if (mount("net_cls", NETCLS_MOUNT_PATH
, "cgroup", 0, "net_cls")) {
538 if (errno
!= EBUSY
) {
539 log_err("mount cgroup net_cls");
543 if (rmdir(NETCLS_MOUNT_PATH
)) {
544 log_err("rmdir cgroup net_cls");
547 if (umount(CGROUP_MOUNT_DFLT
)) {
548 log_err("umount cgroup base");
553 cleanup_classid_environment();
555 if (mkdir(cgroup_workdir
, 0777) && errno
!= EEXIST
) {
556 log_err("mkdir cgroup work dir");
564 * set_classid() - Set a cgroupv1 net_cls classid
566 * Writes the classid into the cgroup work dir's net_cls.classid
567 * file in order to later on trigger socket tagging.
569 * We leverage the current pid as the classid, ensuring unique identification.
571 * On success, it returns 0, otherwise on failure it returns 1. If there
572 * is a failure, it prints the error to stderr.
574 int set_classid(void)
576 char cgroup_workdir
[PATH_MAX
- 42];
577 char cgroup_classid_path
[PATH_MAX
+ 1];
580 format_classid_path(cgroup_workdir
);
581 snprintf(cgroup_classid_path
, sizeof(cgroup_classid_path
),
582 "%s/net_cls.classid", cgroup_workdir
);
584 fd
= open(cgroup_classid_path
, O_WRONLY
);
586 log_err("Opening cgroup classid: %s", cgroup_classid_path
);
590 if (dprintf(fd
, "%u\n", getpid()) < 0) {
591 log_err("Setting cgroup classid");
600 * join_classid() - Join a cgroupv1 net_cls classid
602 * This function expects the cgroup work dir to be already created, as we
603 * join it here. This causes the process sockets to be tagged with the given
606 * On success, it returns 0, otherwise on failure it returns 1.
608 int join_classid(void)
610 char cgroup_workdir
[PATH_MAX
+ 1];
612 format_classid_path(cgroup_workdir
);
613 return join_cgroup_from_top(cgroup_workdir
);
617 * cleanup_classid_environment() - Cleanup the cgroupv1 net_cls environment
619 * At call time, it moves the calling process to the root cgroup, and then
620 * runs the deletion process.
622 * On failure, it will print an error to stderr, and try to continue.
624 void cleanup_classid_environment(void)
626 char cgroup_workdir
[PATH_MAX
+ 1];
628 format_classid_path(cgroup_workdir
);
629 join_cgroup_from_top(NETCLS_MOUNT_PATH
);
630 nftw(cgroup_workdir
, nftwfunc
, WALK_FD_LIMIT
, FTW_DEPTH
| FTW_MOUNT
);
634 * get_classid_cgroup_id - Get the cgroup id of a net_cls cgroup
636 unsigned long long get_classid_cgroup_id(void)
638 char cgroup_workdir
[PATH_MAX
+ 1];
640 format_classid_path(cgroup_workdir
);
641 return get_cgroup_id_from_path(cgroup_workdir
);
645 * get_cgroup1_hierarchy_id - Retrieves the ID of a cgroup1 hierarchy from the cgroup1 subsys name.
646 * @subsys_name: The cgroup1 subsys name, which can be retrieved from /proc/self/cgroup. It can be
647 * a named cgroup like "name=systemd", a controller name like "net_cls", or multi-controllers like
648 * "net_cls,net_prio".
650 int get_cgroup1_hierarchy_id(const char *subsys_name
)
652 char *c
, *c2
, *c3
, *c4
;
661 file
= fopen("/proc/self/cgroup", "r");
663 log_err("fopen /proc/self/cgroup");
667 while (fgets(line
, 1024, file
)) {
669 for (c
= strtok_r(line
, ":", &c2
); c
&& i
< 2; c
= strtok_r(NULL
, ":", &c2
)) {
671 id
= strtol(c
, NULL
, 10);
673 if (!strcmp(c
, subsys_name
)) {
678 /* Multiple subsystems may share one single mount point */
679 for (c3
= strtok_r(c
, ",", &c4
); c3
;
680 c3
= strtok_r(NULL
, ",", &c4
)) {
681 if (!strcmp(c
, subsys_name
)) {
693 return found
? id
: -1;
697 * open_classid() - Open a cgroupv1 net_cls classid
699 * This function expects the cgroup work dir to be already created, as we
702 * On success, it returns the file descriptor. On failure it returns -1.
704 int open_classid(void)
706 char cgroup_workdir
[PATH_MAX
+ 1];
708 format_classid_path(cgroup_workdir
);
709 return open(cgroup_workdir
, O_RDONLY
);