Skip to content

Commit 816fba3

Browse files
committed
Merge branch 'samples-bpf-automated-cgroup-tests'
Sargun Dhillon says: ==================== samples, bpf: Refactor; Add automated tests for cgroups These two patches are around refactoring out some old, reusable code from the existing test_current_task_under_cgroup_user test, and adding a new, automated test. There is some generic cgroupsv2 setup & cleanup code, given that most environment still don't have it setup by default. With this code, we're able to pretty easily add an automated test for future cgroupsv2 functionality. ==================== Signed-off-by: David S. Miller <[email protected]>
2 parents 69a9d09 + 9b474ec commit 816fba3

File tree

5 files changed

+352
-85
lines changed

5 files changed

+352
-85
lines changed

samples/bpf/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ hostprogs-y += map_perf_test
2323
hostprogs-y += test_overhead
2424
hostprogs-y += test_cgrp2_array_pin
2525
hostprogs-y += test_cgrp2_attach
26+
hostprogs-y += test_cgrp2_attach2
2627
hostprogs-y += test_cgrp2_sock
2728
hostprogs-y += test_cgrp2_sock2
2829
hostprogs-y += xdp1
@@ -54,12 +55,13 @@ map_perf_test-objs := bpf_load.o libbpf.o map_perf_test_user.o
5455
test_overhead-objs := bpf_load.o libbpf.o test_overhead_user.o
5556
test_cgrp2_array_pin-objs := libbpf.o test_cgrp2_array_pin.o
5657
test_cgrp2_attach-objs := libbpf.o test_cgrp2_attach.o
58+
test_cgrp2_attach2-objs := libbpf.o test_cgrp2_attach2.o cgroup_helpers.o
5759
test_cgrp2_sock-objs := libbpf.o test_cgrp2_sock.o
5860
test_cgrp2_sock2-objs := bpf_load.o libbpf.o test_cgrp2_sock2.o
5961
xdp1-objs := bpf_load.o libbpf.o xdp1_user.o
6062
# reuse xdp1 source intentionally
6163
xdp2-objs := bpf_load.o libbpf.o xdp1_user.o
62-
test_current_task_under_cgroup-objs := bpf_load.o libbpf.o \
64+
test_current_task_under_cgroup-objs := bpf_load.o libbpf.o cgroup_helpers.o \
6365
test_current_task_under_cgroup_user.o
6466
trace_event-objs := bpf_load.o libbpf.o trace_event_user.o
6567
sampleip-objs := bpf_load.o libbpf.o sampleip_user.o

samples/bpf/cgroup_helpers.c

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#define _GNU_SOURCE
2+
#include <sched.h>
3+
#include <sys/mount.h>
4+
#include <sys/stat.h>
5+
#include <sys/types.h>
6+
#include <linux/limits.h>
7+
#include <stdio.h>
8+
#include <linux/sched.h>
9+
#include <fcntl.h>
10+
#include <unistd.h>
11+
#include <ftw.h>
12+
13+
14+
#include "cgroup_helpers.h"
15+
16+
/*
17+
* To avoid relying on the system setup, when setup_cgroup_env is called
18+
* we create a new mount namespace, and cgroup namespace. The cgroup2
19+
* root is mounted at CGROUP_MOUNT_PATH
20+
*
21+
* Unfortunately, most people don't have cgroupv2 enabled at this point in time.
22+
* It's easier to create our own mount namespace and manage it ourselves.
23+
*
24+
* We assume /mnt exists.
25+
*/
26+
27+
#define WALK_FD_LIMIT 16
28+
#define CGROUP_MOUNT_PATH "/mnt"
29+
#define CGROUP_WORK_DIR "/cgroup-test-work-dir"
30+
#define format_cgroup_path(buf, path) \
31+
snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \
32+
CGROUP_WORK_DIR, path)
33+
34+
/**
35+
* setup_cgroup_environment() - Setup the cgroup environment
36+
*
37+
* After calling this function, cleanup_cgroup_environment should be called
38+
* once testing is complete.
39+
*
40+
* This function will print an error to stderr and return 1 if it is unable
41+
* to setup the cgroup environment. If setup is successful, 0 is returned.
42+
*/
43+
int setup_cgroup_environment(void)
44+
{
45+
char cgroup_workdir[PATH_MAX + 1];
46+
47+
format_cgroup_path(cgroup_workdir, "");
48+
49+
if (unshare(CLONE_NEWNS)) {
50+
log_err("unshare");
51+
return 1;
52+
}
53+
54+
if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
55+
log_err("mount fakeroot");
56+
return 1;
57+
}
58+
59+
if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL)) {
60+
log_err("mount cgroup2");
61+
return 1;
62+
}
63+
64+
/* Cleanup existing failed runs, now that the environment is setup */
65+
cleanup_cgroup_environment();
66+
67+
if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
68+
log_err("mkdir cgroup work dir");
69+
return 1;
70+
}
71+
72+
return 0;
73+
}
74+
75+
static int nftwfunc(const char *filename, const struct stat *statptr,
76+
int fileflags, struct FTW *pfwt)
77+
{
78+
if ((fileflags & FTW_D) && rmdir(filename))
79+
log_err("Removing cgroup: %s", filename);
80+
return 0;
81+
}
82+
83+
84+
static int join_cgroup_from_top(char *cgroup_path)
85+
{
86+
char cgroup_procs_path[PATH_MAX + 1];
87+
pid_t pid = getpid();
88+
int fd, rc = 0;
89+
90+
snprintf(cgroup_procs_path, sizeof(cgroup_procs_path),
91+
"%s/cgroup.procs", cgroup_path);
92+
93+
fd = open(cgroup_procs_path, O_WRONLY);
94+
if (fd < 0) {
95+
log_err("Opening Cgroup Procs: %s", cgroup_procs_path);
96+
return 1;
97+
}
98+
99+
if (dprintf(fd, "%d\n", pid) < 0) {
100+
log_err("Joining Cgroup");
101+
rc = 1;
102+
}
103+
104+
close(fd);
105+
return rc;
106+
}
107+
108+
/**
109+
* join_cgroup() - Join a cgroup
110+
* @path: The cgroup path, relative to the workdir, to join
111+
*
112+
* This function expects a cgroup to already be created, relative to the cgroup
113+
* work dir, and it joins it. For example, passing "/my-cgroup" as the path
114+
* would actually put the calling process into the cgroup
115+
* "/cgroup-test-work-dir/my-cgroup"
116+
*
117+
* On success, it returns 0, otherwise on failure it returns 1.
118+
*/
119+
int join_cgroup(char *path)
120+
{
121+
char cgroup_path[PATH_MAX + 1];
122+
123+
format_cgroup_path(cgroup_path, path);
124+
return join_cgroup_from_top(cgroup_path);
125+
}
126+
127+
/**
128+
* cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
129+
*
130+
* This is an idempotent function to delete all temporary cgroups that
131+
* have been created during the test, including the cgroup testing work
132+
* directory.
133+
*
134+
* At call time, it moves the calling process to the root cgroup, and then
135+
* runs the deletion process. It is idempotent, and should not fail, unless
136+
* a process is lingering.
137+
*
138+
* On failure, it will print an error to stderr, and try to continue.
139+
*/
140+
void cleanup_cgroup_environment(void)
141+
{
142+
char cgroup_workdir[PATH_MAX + 1];
143+
144+
format_cgroup_path(cgroup_workdir, "");
145+
join_cgroup_from_top(CGROUP_MOUNT_PATH);
146+
nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
147+
}
148+
149+
/**
150+
* create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
151+
* @path: The cgroup path, relative to the workdir, to join
152+
*
153+
* This function creates a cgroup under the top level workdir and returns the
154+
* file descriptor. It is idempotent.
155+
*
156+
* On success, it returns the file descriptor. On failure it returns 0.
157+
* If there is a failure, it prints the error to stderr.
158+
*/
159+
int create_and_get_cgroup(char *path)
160+
{
161+
char cgroup_path[PATH_MAX + 1];
162+
int fd;
163+
164+
format_cgroup_path(cgroup_path, path);
165+
if (mkdir(cgroup_path, 0777) && errno != EEXIST) {
166+
log_err("mkdiring cgroup");
167+
return 0;
168+
}
169+
170+
fd = open(cgroup_path, O_RDONLY);
171+
if (fd < 0) {
172+
log_err("Opening Cgroup");
173+
return 0;
174+
}
175+
176+
return fd;
177+
}

samples/bpf/cgroup_helpers.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef __CGROUP_HELPERS_H
2+
#define __CGROUP_HELPERS_H
3+
#include <errno.h>
4+
#include <string.h>
5+
6+
#define clean_errno() (errno == 0 ? "None" : strerror(errno))
7+
#define log_err(MSG, ...) fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", \
8+
__FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
9+
10+
11+
int create_and_get_cgroup(char *path);
12+
int join_cgroup(char *path);
13+
int setup_cgroup_environment(void);
14+
void cleanup_cgroup_environment(void);
15+
16+
#endif

samples/bpf/test_cgrp2_attach2.c

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/* eBPF example program:
2+
*
3+
* - Creates arraymap in kernel with 4 bytes keys and 8 byte values
4+
*
5+
* - Loads eBPF program
6+
*
7+
* The eBPF program accesses the map passed in to store two pieces of
8+
* information. The number of invocations of the program, which maps
9+
* to the number of packets received, is stored to key 0. Key 1 is
10+
* incremented on each iteration by the number of bytes stored in
11+
* the skb.
12+
*
13+
* - Attaches the new program to a cgroup using BPF_PROG_ATTACH
14+
*
15+
* - Every second, reads map[0] and map[1] to see how many bytes and
16+
* packets were seen on any socket of tasks in the given cgroup.
17+
*/
18+
19+
#define _GNU_SOURCE
20+
21+
#include <stdio.h>
22+
#include <stdlib.h>
23+
#include <assert.h>
24+
#include <unistd.h>
25+
26+
#include <linux/bpf.h>
27+
28+
#include "libbpf.h"
29+
#include "cgroup_helpers.h"
30+
31+
#define FOO "/foo"
32+
#define BAR "/foo/bar/"
33+
#define PING_CMD "ping -c1 -w1 127.0.0.1"
34+
35+
static int prog_load(int verdict)
36+
{
37+
int ret;
38+
struct bpf_insn prog[] = {
39+
BPF_MOV64_IMM(BPF_REG_0, verdict), /* r0 = verdict */
40+
BPF_EXIT_INSN(),
41+
};
42+
43+
ret = bpf_prog_load(BPF_PROG_TYPE_CGROUP_SKB,
44+
prog, sizeof(prog), "GPL", 0);
45+
46+
if (ret < 0) {
47+
log_err("Loading program");
48+
printf("Output from verifier:\n%s\n-------\n", bpf_log_buf);
49+
return 0;
50+
}
51+
return ret;
52+
}
53+
54+
55+
int main(int argc, char **argv)
56+
{
57+
int drop_prog, allow_prog, foo = 0, bar = 0, rc = 0;
58+
59+
allow_prog = prog_load(1);
60+
if (!allow_prog)
61+
goto err;
62+
63+
drop_prog = prog_load(0);
64+
if (!drop_prog)
65+
goto err;
66+
67+
if (setup_cgroup_environment())
68+
goto err;
69+
70+
/* Create cgroup /foo, get fd, and join it */
71+
foo = create_and_get_cgroup(FOO);
72+
if (!foo)
73+
goto err;
74+
75+
if (join_cgroup(FOO))
76+
goto err;
77+
78+
if (bpf_prog_attach(drop_prog, foo, BPF_CGROUP_INET_EGRESS)) {
79+
log_err("Attaching prog to /foo");
80+
goto err;
81+
}
82+
83+
assert(system(PING_CMD) != 0);
84+
85+
/* Create cgroup /foo/bar, get fd, and join it */
86+
bar = create_and_get_cgroup(BAR);
87+
if (!bar)
88+
goto err;
89+
90+
if (join_cgroup(BAR))
91+
goto err;
92+
93+
assert(system(PING_CMD) != 0);
94+
95+
if (bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS)) {
96+
log_err("Attaching prog to /foo/bar");
97+
goto err;
98+
}
99+
100+
assert(system(PING_CMD) == 0);
101+
102+
103+
if (bpf_prog_detach(bar, BPF_CGROUP_INET_EGRESS)) {
104+
log_err("Detaching program from /foo/bar");
105+
goto err;
106+
}
107+
108+
assert(system(PING_CMD) != 0);
109+
110+
if (bpf_prog_attach(allow_prog, bar, BPF_CGROUP_INET_EGRESS)) {
111+
log_err("Attaching prog to /foo/bar");
112+
goto err;
113+
}
114+
115+
if (bpf_prog_detach(foo, BPF_CGROUP_INET_EGRESS)) {
116+
log_err("Detaching program from /foo");
117+
goto err;
118+
}
119+
120+
assert(system(PING_CMD) == 0);
121+
122+
goto out;
123+
124+
err:
125+
rc = 1;
126+
127+
out:
128+
close(foo);
129+
close(bar);
130+
cleanup_cgroup_environment();
131+
return rc;
132+
}

0 commit comments

Comments
 (0)