diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile index 13c3e1869890..00cd3081c038 100644 --- a/samples/bpf/Makefile +++ b/samples/bpf/Makefile @@ -23,6 +23,7 @@ hostprogs-y += map_perf_test hostprogs-y += test_overhead hostprogs-y += test_cgrp2_array_pin hostprogs-y += test_cgrp2_attach +hostprogs-y += test_cgrp2_attach2 hostprogs-y += test_cgrp2_sock hostprogs-y += test_cgrp2_sock2 hostprogs-y += xdp1 @@ -54,12 +55,13 @@ map_perf_test-objs := bpf_load.o libbpf.o map_perf_test_user.o test_overhead-objs := bpf_load.o libbpf.o test_overhead_user.o test_cgrp2_array_pin-objs := libbpf.o test_cgrp2_array_pin.o test_cgrp2_attach-objs := libbpf.o test_cgrp2_attach.o +test_cgrp2_attach2-objs := libbpf.o test_cgrp2_attach2.o cgroup_helpers.o test_cgrp2_sock-objs := libbpf.o test_cgrp2_sock.o test_cgrp2_sock2-objs := bpf_load.o libbpf.o test_cgrp2_sock2.o xdp1-objs := bpf_load.o libbpf.o xdp1_user.o # reuse xdp1 source intentionally xdp2-objs := bpf_load.o libbpf.o xdp1_user.o -test_current_task_under_cgroup-objs := bpf_load.o libbpf.o \ +test_current_task_under_cgroup-objs := bpf_load.o libbpf.o cgroup_helpers.o \ test_current_task_under_cgroup_user.o trace_event-objs := bpf_load.o libbpf.o trace_event_user.o sampleip-objs := bpf_load.o libbpf.o sampleip_user.o diff --git a/samples/bpf/cgroup_helpers.c b/samples/bpf/cgroup_helpers.c new file mode 100644 index 000000000000..9d1be9426401 --- /dev/null +++ b/samples/bpf/cgroup_helpers.c @@ -0,0 +1,177 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 cgroup2 + * 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. + */ + +#define WALK_FD_LIMIT 16 +#define CGROUP_MOUNT_PATH "/mnt" +#define CGROUP_WORK_DIR "/cgroup-test-work-dir" +#define format_cgroup_path(buf, path) \ + snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \ + CGROUP_WORK_DIR, path) + +/** + * 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 + 1]; + + 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)) { + 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; + } + + 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(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 + * @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(char *path) +{ + char cgroup_path[PATH_MAX + 1]; + + format_cgroup_path(cgroup_path, 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); +} + +/** + * create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD + * @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 0. + * If there is a failure, it prints the error to stderr. + */ +int create_and_get_cgroup(char *path) +{ + char cgroup_path[PATH_MAX + 1]; + int fd; + + format_cgroup_path(cgroup_path, path); + if (mkdir(cgroup_path, 0777) && errno != EEXIST) { + log_err("mkdiring cgroup"); + return 0; + } + + fd = open(cgroup_path, O_RDONLY); + if (fd < 0) { + log_err("Opening Cgroup"); + return 0; + } + + return fd; +} diff --git a/samples/bpf/cgroup_helpers.h b/samples/bpf/cgroup_helpers.h new file mode 100644 index 000000000000..78c55207b6bd --- /dev/null +++ b/samples/bpf/cgroup_helpers.h @@ -0,0 +1,16 @@ +#ifndef __CGROUP_HELPERS_H +#define __CGROUP_HELPERS_H +#include +#include + +#define clean_errno() (errno == 0 ? "None" : strerror(errno)) +#define log_err(MSG, ...) fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", \ + __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) + + +int create_and_get_cgroup(char *path); +int join_cgroup(char *path); +int setup_cgroup_environment(void); +void cleanup_cgroup_environment(void); + +#endif diff --git a/samples/bpf/test_cgrp2_attach2.c b/samples/bpf/test_cgrp2_attach2.c new file mode 100644 index 000000000000..ddfac42ed4df --- /dev/null +++ b/samples/bpf/test_cgrp2_attach2.c @@ -0,0 +1,132 @@ +/* eBPF example program: + * + * - Creates arraymap in kernel with 4 bytes keys and 8 byte values + * + * - Loads eBPF program + * + * The eBPF program accesses the map passed in to store two pieces of + * information. The number of invocations of the program, which maps + * to the number of packets received, is stored to key 0. Key 1 is + * incremented on each iteration by the number of bytes stored in + * the skb. + * + * - Attaches the new program to a cgroup using BPF_PROG_ATTACH + * + * - Every second, reads map[0] and map[1] to see how many bytes and + * packets were seen on any socket of tasks in the given cgroup. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include + +#include "libbpf.h" +#include "cgroup_helpers.h" + +#define FOO "/foo" +#define BAR "/foo/bar/" +#define PING_CMD "ping -c1 -w1 127.0.0.1" + +static int prog_load(int verdict) +{ + int ret; + struct bpf_insn prog[] = { + BPF_MOV64_IMM(BPF_REG_0, verdict), /* r0 = verdict */ + BPF_EXIT_INSN(), + }; + + ret = bpf_prog_load(BPF_PROG_TYPE_CGROUP_SKB, + prog, sizeof(prog), "GPL", 0); + + if (ret < 0) { + log_err("Loading program"); + printf("Output from verifier:\n%s\n-------\n", bpf_log_buf); + return 0; + } + return ret; +} + + +int main(int argc, char **argv) +{ + int drop_prog, allow_prog, foo = 0, bar = 0, rc = 0; + + allow_prog = prog_load(1); + if (!allow_prog) + goto err; + + drop_prog = prog_load(0); + if (!drop_prog) + goto err; + + if (setup_cgroup_environment()) + goto err; + + /* Create cgroup /foo, get fd, and join it */ + foo = create_and_get_cgroup(FOO); + if (!foo) + goto err; + + if (join_cgroup(FOO)) + goto err; + + if (bpf_prog_attach(drop_prog, foo, BPF_CGROUP_INET_EGRESS)) { + log_err("Attaching prog to /foo"); + goto err; + } + + assert(system(PING_CMD) != 0); + + /* Create cgroup /foo/bar, get fd, and join it */ + bar = create_and_get_cgroup(BAR); + if (!bar) + goto err; + + if (join_cgroup(BAR)) + goto err; + + assert(system(PING_CMD) != 0); + + if (bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS)) { + log_err("Attaching prog to /foo/bar"); + goto err; + } + + assert(system(PING_CMD) == 0); + + + if (bpf_prog_detach(bar, BPF_CGROUP_INET_EGRESS)) { + log_err("Detaching program from /foo/bar"); + goto err; + } + + assert(system(PING_CMD) != 0); + + if (bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS)) { + log_err("Attaching prog to /foo/bar"); + goto err; + } + + if (bpf_prog_detach(foo, BPF_CGROUP_INET_EGRESS)) { + log_err("Detaching program from /foo"); + goto err; + } + + assert(system(PING_CMD) == 0); + + goto out; + +err: + rc = 1; + +out: + close(foo); + close(bar); + cleanup_cgroup_environment(); + return rc; +} diff --git a/samples/bpf/test_current_task_under_cgroup_user.c b/samples/bpf/test_current_task_under_cgroup_user.c index 30b0bce884f9..95aaaa846130 100644 --- a/samples/bpf/test_current_task_under_cgroup_user.c +++ b/samples/bpf/test_current_task_under_cgroup_user.c @@ -11,50 +11,16 @@ #include #include "libbpf.h" #include "bpf_load.h" -#include -#include -#include #include -#include -#include -#include -#include -#include +#include "cgroup_helpers.h" -#define CGROUP_MOUNT_PATH "/mnt" -#define CGROUP_PATH "/mnt/my-cgroup" - -#define clean_errno() (errno == 0 ? "None" : strerror(errno)) -#define log_err(MSG, ...) fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", \ - __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) - -static int join_cgroup(char *path) -{ - int fd, rc = 0; - pid_t pid = getpid(); - char cgroup_path[PATH_MAX + 1]; - - snprintf(cgroup_path, sizeof(cgroup_path), "%s/cgroup.procs", path); - - fd = open(cgroup_path, O_WRONLY); - if (fd < 0) { - log_err("Opening Cgroup"); - return 1; - } - - if (dprintf(fd, "%d\n", pid) < 0) { - log_err("Joining Cgroup"); - rc = 1; - } - close(fd); - return rc; -} +#define CGROUP_PATH "/my-cgroup" int main(int argc, char **argv) { - char filename[256]; - int cg2, idx = 0; pid_t remote_pid, local_pid = getpid(); + int cg2, idx = 0, rc = 0; + char filename[256]; snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]); if (load_bpf_file(filename)) { @@ -62,47 +28,22 @@ int main(int argc, char **argv) return 1; } - /* - * This is to avoid interfering with existing cgroups. 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. - */ - if (unshare(CLONE_NEWNS)) { - log_err("unshare"); - return 1; - } + if (setup_cgroup_environment()) + goto err; - if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) { - log_err("mount fakeroot"); - return 1; - } + cg2 = create_and_get_cgroup(CGROUP_PATH); - if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL)) { - log_err("mount cgroup2"); - return 1; - } - - if (mkdir(CGROUP_PATH, 0777) && errno != EEXIST) { - log_err("mkdir cgroup"); - return 1; - } - - cg2 = open(CGROUP_PATH, O_RDONLY); - if (cg2 < 0) { - log_err("opening target cgroup"); - goto cleanup_cgroup_err; - } + if (!cg2) + goto err; if (bpf_update_elem(map_fd[0], &idx, &cg2, BPF_ANY)) { log_err("Adding target cgroup to map"); - goto cleanup_cgroup_err; - } - if (join_cgroup("/mnt/my-cgroup")) { - log_err("Leaving target cgroup"); - goto cleanup_cgroup_err; + goto err; } + if (join_cgroup(CGROUP_PATH)) + goto err; + /* * The installed helper program catched the sync call, and should * write it to the map. @@ -115,12 +56,12 @@ int main(int argc, char **argv) fprintf(stderr, "BPF Helper didn't write correct PID to map, but: %d\n", remote_pid); - goto leave_cgroup_err; + goto err; } /* Verify the negative scenario; leave the cgroup */ - if (join_cgroup(CGROUP_MOUNT_PATH)) - goto leave_cgroup_err; + if (join_cgroup("/")) + goto err; remote_pid = 0; bpf_update_elem(map_fd[1], &idx, &remote_pid, BPF_ANY); @@ -130,16 +71,15 @@ int main(int argc, char **argv) if (local_pid == remote_pid) { fprintf(stderr, "BPF cgroup negative test did not work\n"); - goto cleanup_cgroup_err; + goto err; } - rmdir(CGROUP_PATH); - return 0; + goto out; +err: + rc = 1; - /* Error condition, cleanup */ -leave_cgroup_err: - join_cgroup(CGROUP_MOUNT_PATH); -cleanup_cgroup_err: - rmdir(CGROUP_PATH); - return 1; +out: + close(cg2); + cleanup_cgroup_environment(); + return rc; }