xref: /openbmc/linux/tools/bpf/bpftool/cgroup.c (revision e8ec0493)
1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2 // Copyright (C) 2017 Facebook
3 // Author: Roman Gushchin <guro@fb.com>
4 
5 #define _XOPEN_SOURCE 500
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <ftw.h>
9 #include <mntent.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <unistd.h>
16 
17 #include <bpf/bpf.h>
18 
19 #include "main.h"
20 
21 #define HELP_SPEC_ATTACH_FLAGS						\
22 	"ATTACH_FLAGS := { multi | override }"
23 
24 #define HELP_SPEC_ATTACH_TYPES						       \
25 	"       ATTACH_TYPE := { ingress | egress | sock_create |\n"	       \
26 	"                        sock_ops | device | bind4 | bind6 |\n"	       \
27 	"                        post_bind4 | post_bind6 | connect4 |\n"       \
28 	"                        connect6 | getpeername4 | getpeername6 |\n"   \
29 	"                        getsockname4 | getsockname6 | sendmsg4 |\n"   \
30 	"                        sendmsg6 | recvmsg4 | recvmsg6 |\n"           \
31 	"                        sysctl | getsockopt | setsockopt }"
32 
33 static unsigned int query_flags;
34 
35 static enum bpf_attach_type parse_attach_type(const char *str)
36 {
37 	enum bpf_attach_type type;
38 
39 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
40 		if (attach_type_name[type] &&
41 		    is_prefix(str, attach_type_name[type]))
42 			return type;
43 	}
44 
45 	return __MAX_BPF_ATTACH_TYPE;
46 }
47 
48 static int show_bpf_prog(int id, enum bpf_attach_type attach_type,
49 			 const char *attach_flags_str,
50 			 int level)
51 {
52 	struct bpf_prog_info info = {};
53 	__u32 info_len = sizeof(info);
54 	int prog_fd;
55 
56 	prog_fd = bpf_prog_get_fd_by_id(id);
57 	if (prog_fd < 0)
58 		return -1;
59 
60 	if (bpf_obj_get_info_by_fd(prog_fd, &info, &info_len)) {
61 		close(prog_fd);
62 		return -1;
63 	}
64 
65 	if (json_output) {
66 		jsonw_start_object(json_wtr);
67 		jsonw_uint_field(json_wtr, "id", info.id);
68 		if (attach_type < ARRAY_SIZE(attach_type_name))
69 			jsonw_string_field(json_wtr, "attach_type",
70 					   attach_type_name[attach_type]);
71 		else
72 			jsonw_uint_field(json_wtr, "attach_type", attach_type);
73 		jsonw_string_field(json_wtr, "attach_flags",
74 				   attach_flags_str);
75 		jsonw_string_field(json_wtr, "name", info.name);
76 		jsonw_end_object(json_wtr);
77 	} else {
78 		printf("%s%-8u ", level ? "    " : "", info.id);
79 		if (attach_type < ARRAY_SIZE(attach_type_name))
80 			printf("%-15s", attach_type_name[attach_type]);
81 		else
82 			printf("type %-10u", attach_type);
83 		printf(" %-15s %-15s\n", attach_flags_str, info.name);
84 	}
85 
86 	close(prog_fd);
87 	return 0;
88 }
89 
90 static int count_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type)
91 {
92 	__u32 prog_cnt = 0;
93 	int ret;
94 
95 	ret = bpf_prog_query(cgroup_fd, type, query_flags, NULL,
96 			     NULL, &prog_cnt);
97 	if (ret)
98 		return -1;
99 
100 	return prog_cnt;
101 }
102 
103 static int cgroup_has_attached_progs(int cgroup_fd)
104 {
105 	enum bpf_attach_type type;
106 	bool no_prog = true;
107 
108 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
109 		int count = count_attached_bpf_progs(cgroup_fd, type);
110 
111 		if (count < 0 && errno != EINVAL)
112 			return -1;
113 
114 		if (count > 0) {
115 			no_prog = false;
116 			break;
117 		}
118 	}
119 
120 	return no_prog ? 0 : 1;
121 }
122 static int show_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
123 				   int level)
124 {
125 	const char *attach_flags_str;
126 	__u32 prog_ids[1024] = {0};
127 	__u32 prog_cnt, iter;
128 	__u32 attach_flags;
129 	char buf[32];
130 	int ret;
131 
132 	prog_cnt = ARRAY_SIZE(prog_ids);
133 	ret = bpf_prog_query(cgroup_fd, type, query_flags, &attach_flags,
134 			     prog_ids, &prog_cnt);
135 	if (ret)
136 		return ret;
137 
138 	if (prog_cnt == 0)
139 		return 0;
140 
141 	switch (attach_flags) {
142 	case BPF_F_ALLOW_MULTI:
143 		attach_flags_str = "multi";
144 		break;
145 	case BPF_F_ALLOW_OVERRIDE:
146 		attach_flags_str = "override";
147 		break;
148 	case 0:
149 		attach_flags_str = "";
150 		break;
151 	default:
152 		snprintf(buf, sizeof(buf), "unknown(%x)", attach_flags);
153 		attach_flags_str = buf;
154 	}
155 
156 	for (iter = 0; iter < prog_cnt; iter++)
157 		show_bpf_prog(prog_ids[iter], type,
158 			      attach_flags_str, level);
159 
160 	return 0;
161 }
162 
163 static int do_show(int argc, char **argv)
164 {
165 	enum bpf_attach_type type;
166 	int has_attached_progs;
167 	const char *path;
168 	int cgroup_fd;
169 	int ret = -1;
170 
171 	query_flags = 0;
172 
173 	if (!REQ_ARGS(1))
174 		return -1;
175 	path = GET_ARG();
176 
177 	while (argc) {
178 		if (is_prefix(*argv, "effective")) {
179 			if (query_flags & BPF_F_QUERY_EFFECTIVE) {
180 				p_err("duplicated argument: %s", *argv);
181 				return -1;
182 			}
183 			query_flags |= BPF_F_QUERY_EFFECTIVE;
184 			NEXT_ARG();
185 		} else {
186 			p_err("expected no more arguments, 'effective', got: '%s'?",
187 			      *argv);
188 			return -1;
189 		}
190 	}
191 
192 	cgroup_fd = open(path, O_RDONLY);
193 	if (cgroup_fd < 0) {
194 		p_err("can't open cgroup %s", path);
195 		goto exit;
196 	}
197 
198 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
199 	if (has_attached_progs < 0) {
200 		p_err("can't query bpf programs attached to %s: %s",
201 		      path, strerror(errno));
202 		goto exit_cgroup;
203 	} else if (!has_attached_progs) {
204 		ret = 0;
205 		goto exit_cgroup;
206 	}
207 
208 	if (json_output)
209 		jsonw_start_array(json_wtr);
210 	else
211 		printf("%-8s %-15s %-15s %-15s\n", "ID", "AttachType",
212 		       "AttachFlags", "Name");
213 
214 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
215 		/*
216 		 * Not all attach types may be supported, so it's expected,
217 		 * that some requests will fail.
218 		 * If we were able to get the show for at least one
219 		 * attach type, let's return 0.
220 		 */
221 		if (show_attached_bpf_progs(cgroup_fd, type, 0) == 0)
222 			ret = 0;
223 	}
224 
225 	if (json_output)
226 		jsonw_end_array(json_wtr);
227 
228 exit_cgroup:
229 	close(cgroup_fd);
230 exit:
231 	return ret;
232 }
233 
234 /*
235  * To distinguish nftw() errors and do_show_tree_fn() errors
236  * and avoid duplicating error messages, let's return -2
237  * from do_show_tree_fn() in case of error.
238  */
239 #define NFTW_ERR		-1
240 #define SHOW_TREE_FN_ERR	-2
241 static int do_show_tree_fn(const char *fpath, const struct stat *sb,
242 			   int typeflag, struct FTW *ftw)
243 {
244 	enum bpf_attach_type type;
245 	int has_attached_progs;
246 	int cgroup_fd;
247 
248 	if (typeflag != FTW_D)
249 		return 0;
250 
251 	cgroup_fd = open(fpath, O_RDONLY);
252 	if (cgroup_fd < 0) {
253 		p_err("can't open cgroup %s: %s", fpath, strerror(errno));
254 		return SHOW_TREE_FN_ERR;
255 	}
256 
257 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
258 	if (has_attached_progs < 0) {
259 		p_err("can't query bpf programs attached to %s: %s",
260 		      fpath, strerror(errno));
261 		close(cgroup_fd);
262 		return SHOW_TREE_FN_ERR;
263 	} else if (!has_attached_progs) {
264 		close(cgroup_fd);
265 		return 0;
266 	}
267 
268 	if (json_output) {
269 		jsonw_start_object(json_wtr);
270 		jsonw_string_field(json_wtr, "cgroup", fpath);
271 		jsonw_name(json_wtr, "programs");
272 		jsonw_start_array(json_wtr);
273 	} else {
274 		printf("%s\n", fpath);
275 	}
276 
277 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++)
278 		show_attached_bpf_progs(cgroup_fd, type, ftw->level);
279 
280 	if (errno == EINVAL)
281 		/* Last attach type does not support query.
282 		 * Do not report an error for this, especially because batch
283 		 * mode would stop processing commands.
284 		 */
285 		errno = 0;
286 
287 	if (json_output) {
288 		jsonw_end_array(json_wtr);
289 		jsonw_end_object(json_wtr);
290 	}
291 
292 	close(cgroup_fd);
293 
294 	return 0;
295 }
296 
297 static char *find_cgroup_root(void)
298 {
299 	struct mntent *mnt;
300 	FILE *f;
301 
302 	f = fopen("/proc/mounts", "r");
303 	if (f == NULL)
304 		return NULL;
305 
306 	while ((mnt = getmntent(f))) {
307 		if (strcmp(mnt->mnt_type, "cgroup2") == 0) {
308 			fclose(f);
309 			return strdup(mnt->mnt_dir);
310 		}
311 	}
312 
313 	fclose(f);
314 	return NULL;
315 }
316 
317 static int do_show_tree(int argc, char **argv)
318 {
319 	char *cgroup_root, *cgroup_alloced = NULL;
320 	int ret;
321 
322 	query_flags = 0;
323 
324 	if (!argc) {
325 		cgroup_alloced = find_cgroup_root();
326 		if (!cgroup_alloced) {
327 			p_err("cgroup v2 isn't mounted");
328 			return -1;
329 		}
330 		cgroup_root = cgroup_alloced;
331 	} else {
332 		cgroup_root = GET_ARG();
333 
334 		while (argc) {
335 			if (is_prefix(*argv, "effective")) {
336 				if (query_flags & BPF_F_QUERY_EFFECTIVE) {
337 					p_err("duplicated argument: %s", *argv);
338 					return -1;
339 				}
340 				query_flags |= BPF_F_QUERY_EFFECTIVE;
341 				NEXT_ARG();
342 			} else {
343 				p_err("expected no more arguments, 'effective', got: '%s'?",
344 				      *argv);
345 				return -1;
346 			}
347 		}
348 	}
349 
350 	if (json_output)
351 		jsonw_start_array(json_wtr);
352 	else
353 		printf("%s\n"
354 		       "%-8s %-15s %-15s %-15s\n",
355 		       "CgroupPath",
356 		       "ID", "AttachType", "AttachFlags", "Name");
357 
358 	switch (nftw(cgroup_root, do_show_tree_fn, 1024, FTW_MOUNT)) {
359 	case NFTW_ERR:
360 		p_err("can't iterate over %s: %s", cgroup_root,
361 		      strerror(errno));
362 		ret = -1;
363 		break;
364 	case SHOW_TREE_FN_ERR:
365 		ret = -1;
366 		break;
367 	default:
368 		ret = 0;
369 	}
370 
371 	if (json_output)
372 		jsonw_end_array(json_wtr);
373 
374 	free(cgroup_alloced);
375 
376 	return ret;
377 }
378 
379 static int do_attach(int argc, char **argv)
380 {
381 	enum bpf_attach_type attach_type;
382 	int cgroup_fd, prog_fd;
383 	int attach_flags = 0;
384 	int ret = -1;
385 	int i;
386 
387 	if (argc < 4) {
388 		p_err("too few parameters for cgroup attach");
389 		goto exit;
390 	}
391 
392 	cgroup_fd = open(argv[0], O_RDONLY);
393 	if (cgroup_fd < 0) {
394 		p_err("can't open cgroup %s", argv[0]);
395 		goto exit;
396 	}
397 
398 	attach_type = parse_attach_type(argv[1]);
399 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
400 		p_err("invalid attach type");
401 		goto exit_cgroup;
402 	}
403 
404 	argc -= 2;
405 	argv = &argv[2];
406 	prog_fd = prog_parse_fd(&argc, &argv);
407 	if (prog_fd < 0)
408 		goto exit_cgroup;
409 
410 	for (i = 0; i < argc; i++) {
411 		if (is_prefix(argv[i], "multi")) {
412 			attach_flags |= BPF_F_ALLOW_MULTI;
413 		} else if (is_prefix(argv[i], "override")) {
414 			attach_flags |= BPF_F_ALLOW_OVERRIDE;
415 		} else {
416 			p_err("unknown option: %s", argv[i]);
417 			goto exit_cgroup;
418 		}
419 	}
420 
421 	if (bpf_prog_attach(prog_fd, cgroup_fd, attach_type, attach_flags)) {
422 		p_err("failed to attach program");
423 		goto exit_prog;
424 	}
425 
426 	if (json_output)
427 		jsonw_null(json_wtr);
428 
429 	ret = 0;
430 
431 exit_prog:
432 	close(prog_fd);
433 exit_cgroup:
434 	close(cgroup_fd);
435 exit:
436 	return ret;
437 }
438 
439 static int do_detach(int argc, char **argv)
440 {
441 	enum bpf_attach_type attach_type;
442 	int prog_fd, cgroup_fd;
443 	int ret = -1;
444 
445 	if (argc < 4) {
446 		p_err("too few parameters for cgroup detach");
447 		goto exit;
448 	}
449 
450 	cgroup_fd = open(argv[0], O_RDONLY);
451 	if (cgroup_fd < 0) {
452 		p_err("can't open cgroup %s", argv[0]);
453 		goto exit;
454 	}
455 
456 	attach_type = parse_attach_type(argv[1]);
457 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
458 		p_err("invalid attach type");
459 		goto exit_cgroup;
460 	}
461 
462 	argc -= 2;
463 	argv = &argv[2];
464 	prog_fd = prog_parse_fd(&argc, &argv);
465 	if (prog_fd < 0)
466 		goto exit_cgroup;
467 
468 	if (bpf_prog_detach2(prog_fd, cgroup_fd, attach_type)) {
469 		p_err("failed to detach program");
470 		goto exit_prog;
471 	}
472 
473 	if (json_output)
474 		jsonw_null(json_wtr);
475 
476 	ret = 0;
477 
478 exit_prog:
479 	close(prog_fd);
480 exit_cgroup:
481 	close(cgroup_fd);
482 exit:
483 	return ret;
484 }
485 
486 static int do_help(int argc, char **argv)
487 {
488 	if (json_output) {
489 		jsonw_null(json_wtr);
490 		return 0;
491 	}
492 
493 	fprintf(stderr,
494 		"Usage: %1$s %2$s { show | list } CGROUP [**effective**]\n"
495 		"       %1$s %2$s tree [CGROUP_ROOT] [**effective**]\n"
496 		"       %1$s %2$s attach CGROUP ATTACH_TYPE PROG [ATTACH_FLAGS]\n"
497 		"       %1$s %2$s detach CGROUP ATTACH_TYPE PROG\n"
498 		"       %1$s %2$s help\n"
499 		"\n"
500 		HELP_SPEC_ATTACH_TYPES "\n"
501 		"       " HELP_SPEC_ATTACH_FLAGS "\n"
502 		"       " HELP_SPEC_PROGRAM "\n"
503 		"       " HELP_SPEC_OPTIONS "\n"
504 		"",
505 		bin_name, argv[-2]);
506 
507 	return 0;
508 }
509 
510 static const struct cmd cmds[] = {
511 	{ "show",	do_show },
512 	{ "list",	do_show },
513 	{ "tree",       do_show_tree },
514 	{ "attach",	do_attach },
515 	{ "detach",	do_detach },
516 	{ "help",	do_help },
517 	{ 0 }
518 };
519 
520 int do_cgroup(int argc, char **argv)
521 {
522 	return cmd_select(cmds, argc, argv, do_help);
523 }
524