Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 | // SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include <sched.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/types.h> #include <linux/limits.h> #include <stdio.h> #include <stdlib.h> #include <linux/sched.h> #include <fcntl.h> #include <unistd.h> #include <ftw.h> #include "cgroup_helpers.h" /* * To avoid relying on the system setup, when setup_cgroup_env is called * we create a new mount namespace, and cgroup namespace. The cgroupv2 * root is mounted at CGROUP_MOUNT_PATH. Unfortunately, most people don't * have cgroupv2 enabled at this point in time. It's easier to create our * own mount namespace and manage it ourselves. We assume /mnt exists. * * Related cgroupv1 helpers are named *classid*(), since we only use the * net_cls controller for tagging net_cls.classid. We assume the default * mount under /sys/fs/cgroup/net_cls, which should be the case for the * vast majority of users. */ #define WALK_FD_LIMIT 16 #define CGROUP_MOUNT_PATH "/mnt" #define CGROUP_MOUNT_DFLT "/sys/fs/cgroup" #define NETCLS_MOUNT_PATH CGROUP_MOUNT_DFLT "/net_cls" #define CGROUP_WORK_DIR "/cgroup-test-work-dir" #define format_cgroup_path_pid(buf, path, pid) \ snprintf(buf, sizeof(buf), "%s%s%d%s", CGROUP_MOUNT_PATH, \ CGROUP_WORK_DIR, pid, path) #define format_cgroup_path(buf, path) \ format_cgroup_path_pid(buf, path, getpid()) #define format_parent_cgroup_path(buf, path) \ format_cgroup_path_pid(buf, path, getppid()) #define format_classid_path(buf) \ snprintf(buf, sizeof(buf), "%s%s", NETCLS_MOUNT_PATH, \ CGROUP_WORK_DIR) static int __enable_controllers(const char *cgroup_path, const char *controllers) { char path[PATH_MAX + 1]; char enable[PATH_MAX + 1]; char *c, *c2; int fd, cfd; ssize_t len; /* If not controllers are passed, enable all available controllers */ if (!controllers) { snprintf(path, sizeof(path), "%s/cgroup.controllers", cgroup_path); fd = open(path, O_RDONLY); if (fd < 0) { log_err("Opening cgroup.controllers: %s", path); return 1; } len = read(fd, enable, sizeof(enable) - 1); if (len < 0) { close(fd); log_err("Reading cgroup.controllers: %s", path); return 1; } else if (len == 0) { /* No controllers to enable */ close(fd); return 0; } enable[len] = 0; close(fd); } else { strncpy(enable, controllers, sizeof(enable)); } snprintf(path, sizeof(path), "%s/cgroup.subtree_control", cgroup_path); cfd = open(path, O_RDWR); if (cfd < 0) { log_err("Opening cgroup.subtree_control: %s", path); return 1; } for (c = strtok_r(enable, " ", &c2); c; c = strtok_r(NULL, " ", &c2)) { if (dprintf(cfd, "+%s\n", c) <= 0) { log_err("Enabling controller %s: %s", c, path); close(cfd); return 1; } } close(cfd); return 0; } /** * enable_controllers() - Enable cgroup v2 controllers * @relative_path: The cgroup path, relative to the workdir * @controllers: List of controllers to enable in cgroup.controllers format * * * Enable given cgroup v2 controllers, if @controllers is NULL, enable all * available controllers. * * If successful, 0 is returned. */ int enable_controllers(const char *relative_path, const char *controllers) { char cgroup_path[PATH_MAX + 1]; format_cgroup_path(cgroup_path, relative_path); return __enable_controllers(cgroup_path, controllers); } static int __write_cgroup_file(const char *cgroup_path, const char *file, const char *buf) { char file_path[PATH_MAX + 1]; int fd; snprintf(file_path, sizeof(file_path), "%s/%s", cgroup_path, file); fd = open(file_path, O_RDWR); if (fd < 0) { log_err("Opening %s", file_path); return 1; } if (dprintf(fd, "%s", buf) <= 0) { log_err("Writing to %s", file_path); close(fd); return 1; } close(fd); return 0; } /** * write_cgroup_file() - Write to a cgroup file * @relative_path: The cgroup path, relative to the workdir * @file: The name of the file in cgroupfs to write to * @buf: Buffer to write to the file * * Write to a file in the given cgroup's directory. * * If successful, 0 is returned. */ int write_cgroup_file(const char *relative_path, const char *file, const char *buf) { char cgroup_path[PATH_MAX - 24]; format_cgroup_path(cgroup_path, relative_path); return __write_cgroup_file(cgroup_path, file, buf); } /** * write_cgroup_file_parent() - Write to a cgroup file in the parent process * workdir * @relative_path: The cgroup path, relative to the parent process workdir * @file: The name of the file in cgroupfs to write to * @buf: Buffer to write to the file * * Write to a file in the given cgroup's directory under the parent process * workdir. * * If successful, 0 is returned. */ int write_cgroup_file_parent(const char *relative_path, const char *file, const char *buf) { char cgroup_path[PATH_MAX - 24]; format_parent_cgroup_path(cgroup_path, relative_path); return __write_cgroup_file(cgroup_path, file, buf); } /** * setup_cgroup_environment() - Setup the cgroup environment * * After calling this function, cleanup_cgroup_environment should be called * once testing is complete. * * This function will print an error to stderr and return 1 if it is unable * to setup the cgroup environment. If setup is successful, 0 is returned. */ int setup_cgroup_environment(void) { char cgroup_workdir[PATH_MAX - 24]; format_cgroup_path(cgroup_workdir, ""); if (unshare(CLONE_NEWNS)) { log_err("unshare"); return 1; } if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) { log_err("mount fakeroot"); return 1; } if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) { log_err("mount cgroup2"); return 1; } /* Cleanup existing failed runs, now that the environment is setup */ cleanup_cgroup_environment(); if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) { log_err("mkdir cgroup work dir"); return 1; } /* Enable all available controllers to increase test coverage */ if (__enable_controllers(CGROUP_MOUNT_PATH, NULL) || __enable_controllers(cgroup_workdir, NULL)) return 1; return 0; } static int nftwfunc(const char *filename, const struct stat *statptr, int fileflags, struct FTW *pfwt) { if ((fileflags & FTW_D) && rmdir(filename)) log_err("Removing cgroup: %s", filename); return 0; } static int join_cgroup_from_top(const char *cgroup_path) { char cgroup_procs_path[PATH_MAX + 1]; pid_t pid = getpid(); int fd, rc = 0; snprintf(cgroup_procs_path, sizeof(cgroup_procs_path), "%s/cgroup.procs", cgroup_path); fd = open(cgroup_procs_path, O_WRONLY); if (fd < 0) { log_err("Opening Cgroup Procs: %s", cgroup_procs_path); return 1; } if (dprintf(fd, "%d\n", pid) < 0) { log_err("Joining Cgroup"); rc = 1; } close(fd); return rc; } /** * join_cgroup() - Join a cgroup * @relative_path: The cgroup path, relative to the workdir, to join * * This function expects a cgroup to already be created, relative to the cgroup * work dir, and it joins it. For example, passing "/my-cgroup" as the path * would actually put the calling process into the cgroup * "/cgroup-test-work-dir/my-cgroup" * * On success, it returns 0, otherwise on failure it returns 1. */ int join_cgroup(const char *relative_path) { char cgroup_path[PATH_MAX + 1]; format_cgroup_path(cgroup_path, relative_path); return join_cgroup_from_top(cgroup_path); } /** * join_parent_cgroup() - Join a cgroup in the parent process workdir * @relative_path: The cgroup path, relative to parent process workdir, to join * * See join_cgroup(). * * On success, it returns 0, otherwise on failure it returns 1. */ int join_parent_cgroup(const char *relative_path) { char cgroup_path[PATH_MAX + 1]; format_parent_cgroup_path(cgroup_path, relative_path); return join_cgroup_from_top(cgroup_path); } /** * cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment * * This is an idempotent function to delete all temporary cgroups that * have been created during the test, including the cgroup testing work * directory. * * At call time, it moves the calling process to the root cgroup, and then * runs the deletion process. It is idempotent, and should not fail, unless * a process is lingering. * * On failure, it will print an error to stderr, and try to continue. */ void cleanup_cgroup_environment(void) { char cgroup_workdir[PATH_MAX + 1]; format_cgroup_path(cgroup_workdir, ""); join_cgroup_from_top(CGROUP_MOUNT_PATH); nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT); } /** * get_root_cgroup() - Get the FD of the root cgroup * * On success, it returns the file descriptor. On failure, it returns -1. * If there is a failure, it prints the error to stderr. */ int get_root_cgroup(void) { int fd; fd = open(CGROUP_MOUNT_PATH, O_RDONLY); if (fd < 0) { log_err("Opening root cgroup"); return -1; } return fd; } /** * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD * @relative_path: The cgroup path, relative to the workdir, to join * * This function creates a cgroup under the top level workdir and returns the * file descriptor. It is idempotent. * * On success, it returns the file descriptor. On failure it returns -1. * If there is a failure, it prints the error to stderr. */ int create_and_get_cgroup(const char *relative_path) { char cgroup_path[PATH_MAX + 1]; int fd; format_cgroup_path(cgroup_path, relative_path); if (mkdir(cgroup_path, 0777) && errno != EEXIST) { log_err("mkdiring cgroup %s .. %s", relative_path, cgroup_path); return -1; } fd = open(cgroup_path, O_RDONLY); if (fd < 0) { log_err("Opening Cgroup"); return -1; } return fd; } /** * get_cgroup_id() - Get cgroup id for a particular cgroup path * @relative_path: The cgroup path, relative to the workdir, to join * * On success, it returns the cgroup id. On failure it returns 0, * which is an invalid cgroup id. * If there is a failure, it prints the error to stderr. */ unsigned long long get_cgroup_id(const char *relative_path) { int dirfd, err, flags, mount_id, fhsize; union { unsigned long long cgid; unsigned char raw_bytes[8]; } id; char cgroup_workdir[PATH_MAX + 1]; struct file_handle *fhp, *fhp2; unsigned long long ret = 0; format_cgroup_path(cgroup_workdir, relative_path); dirfd = AT_FDCWD; flags = 0; fhsize = sizeof(*fhp); fhp = calloc(1, fhsize); if (!fhp) { log_err("calloc"); return 0; } err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags); if (err >= 0 || fhp->handle_bytes != 8) { log_err("name_to_handle_at"); goto free_mem; } fhsize = sizeof(struct file_handle) + fhp->handle_bytes; fhp2 = realloc(fhp, fhsize); if (!fhp2) { log_err("realloc"); goto free_mem; } err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags); fhp = fhp2; if (err < 0) { log_err("name_to_handle_at"); goto free_mem; } memcpy(id.raw_bytes, fhp->f_handle, 8); ret = id.cgid; free_mem: free(fhp); return ret; } int cgroup_setup_and_join(const char *path) { int cg_fd; if (setup_cgroup_environment()) { fprintf(stderr, "Failed to setup cgroup environment\n"); return -EINVAL; } cg_fd = create_and_get_cgroup(path); if (cg_fd < 0) { fprintf(stderr, "Failed to create test cgroup\n"); cleanup_cgroup_environment(); return cg_fd; } if (join_cgroup(path)) { fprintf(stderr, "Failed to join cgroup\n"); cleanup_cgroup_environment(); return -EINVAL; } return cg_fd; } /** * setup_classid_environment() - Setup the cgroupv1 net_cls environment * * After calling this function, cleanup_classid_environment should be called * once testing is complete. * * This function will print an error to stderr and return 1 if it is unable * to setup the cgroup environment. If setup is successful, 0 is returned. */ int setup_classid_environment(void) { char cgroup_workdir[PATH_MAX + 1]; format_classid_path(cgroup_workdir); if (mount("tmpfs", CGROUP_MOUNT_DFLT, "tmpfs", 0, NULL) && errno != EBUSY) { log_err("mount cgroup base"); return 1; } if (mkdir(NETCLS_MOUNT_PATH, 0777) && errno != EEXIST) { log_err("mkdir cgroup net_cls"); return 1; } if (mount("net_cls", NETCLS_MOUNT_PATH, "cgroup", 0, "net_cls") && errno != EBUSY) { log_err("mount cgroup net_cls"); return 1; } cleanup_classid_environment(); if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) { log_err("mkdir cgroup work dir"); return 1; } return 0; } /** * set_classid() - Set a cgroupv1 net_cls classid * @id: the numeric classid * * Writes the passed classid into the cgroup work dir's net_cls.classid * file in order to later on trigger socket tagging. * * On success, it returns 0, otherwise on failure it returns 1. If there * is a failure, it prints the error to stderr. */ int set_classid(unsigned int id) { char cgroup_workdir[PATH_MAX - 42]; char cgroup_classid_path[PATH_MAX + 1]; int fd, rc = 0; format_classid_path(cgroup_workdir); snprintf(cgroup_classid_path, sizeof(cgroup_classid_path), "%s/net_cls.classid", cgroup_workdir); fd = open(cgroup_classid_path, O_WRONLY); if (fd < 0) { log_err("Opening cgroup classid: %s", cgroup_classid_path); return 1; } if (dprintf(fd, "%u\n", id) < 0) { log_err("Setting cgroup classid"); rc = 1; } close(fd); return rc; } /** * join_classid() - Join a cgroupv1 net_cls classid * * This function expects the cgroup work dir to be already created, as we * join it here. This causes the process sockets to be tagged with the given * net_cls classid. * * On success, it returns 0, otherwise on failure it returns 1. */ int join_classid(void) { char cgroup_workdir[PATH_MAX + 1]; format_classid_path(cgroup_workdir); return join_cgroup_from_top(cgroup_workdir); } /** * cleanup_classid_environment() - Cleanup the cgroupv1 net_cls environment * * At call time, it moves the calling process to the root cgroup, and then * runs the deletion process. * * On failure, it will print an error to stderr, and try to continue. */ void cleanup_classid_environment(void) { char cgroup_workdir[PATH_MAX + 1]; format_classid_path(cgroup_workdir); join_cgroup_from_top(NETCLS_MOUNT_PATH); nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT); } |