xref: /openbmc/linux/tools/bpf/bpftool/cgroup.c (revision 54a611b6)
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 #include <bpf/btf.h>
19 
20 #include "main.h"
21 
22 #define HELP_SPEC_ATTACH_FLAGS						\
23 	"ATTACH_FLAGS := { multi | override }"
24 
25 #define HELP_SPEC_ATTACH_TYPES						\
26 	"       ATTACH_TYPE := { cgroup_inet_ingress | cgroup_inet_egress |\n" \
27 	"                        cgroup_inet_sock_create | cgroup_sock_ops |\n" \
28 	"                        cgroup_device | cgroup_inet4_bind |\n" \
29 	"                        cgroup_inet6_bind | cgroup_inet4_post_bind |\n" \
30 	"                        cgroup_inet6_post_bind | cgroup_inet4_connect |\n" \
31 	"                        cgroup_inet6_connect | cgroup_inet4_getpeername |\n" \
32 	"                        cgroup_inet6_getpeername | cgroup_inet4_getsockname |\n" \
33 	"                        cgroup_inet6_getsockname | cgroup_udp4_sendmsg |\n" \
34 	"                        cgroup_udp6_sendmsg | cgroup_udp4_recvmsg |\n" \
35 	"                        cgroup_udp6_recvmsg | cgroup_sysctl |\n" \
36 	"                        cgroup_getsockopt | cgroup_setsockopt |\n" \
37 	"                        cgroup_inet_sock_release }"
38 
39 static unsigned int query_flags;
40 static struct btf *btf_vmlinux;
41 static __u32 btf_vmlinux_id;
42 
43 static enum bpf_attach_type parse_attach_type(const char *str)
44 {
45 	const char *attach_type_str;
46 	enum bpf_attach_type type;
47 
48 	for (type = 0; ; type++) {
49 		attach_type_str = libbpf_bpf_attach_type_str(type);
50 		if (!attach_type_str)
51 			break;
52 		if (!strcmp(str, attach_type_str))
53 			return type;
54 	}
55 
56 	/* Also check traditionally used attach type strings. For these we keep
57 	 * allowing prefixed usage.
58 	 */
59 	for (type = 0; ; type++) {
60 		attach_type_str = bpf_attach_type_input_str(type);
61 		if (!attach_type_str)
62 			break;
63 		if (is_prefix(str, attach_type_str))
64 			return type;
65 	}
66 
67 	return __MAX_BPF_ATTACH_TYPE;
68 }
69 
70 static void guess_vmlinux_btf_id(__u32 attach_btf_obj_id)
71 {
72 	struct bpf_btf_info btf_info = {};
73 	__u32 btf_len = sizeof(btf_info);
74 	char name[16] = {};
75 	int err;
76 	int fd;
77 
78 	btf_info.name = ptr_to_u64(name);
79 	btf_info.name_len = sizeof(name);
80 
81 	fd = bpf_btf_get_fd_by_id(attach_btf_obj_id);
82 	if (fd < 0)
83 		return;
84 
85 	err = bpf_obj_get_info_by_fd(fd, &btf_info, &btf_len);
86 	if (err)
87 		goto out;
88 
89 	if (btf_info.kernel_btf && strncmp(name, "vmlinux", sizeof(name)) == 0)
90 		btf_vmlinux_id = btf_info.id;
91 
92 out:
93 	close(fd);
94 }
95 
96 static int show_bpf_prog(int id, enum bpf_attach_type attach_type,
97 			 const char *attach_flags_str,
98 			 int level)
99 {
100 	char prog_name[MAX_PROG_FULL_NAME];
101 	const char *attach_btf_name = NULL;
102 	struct bpf_prog_info info = {};
103 	const char *attach_type_str;
104 	__u32 info_len = sizeof(info);
105 	int prog_fd;
106 
107 	prog_fd = bpf_prog_get_fd_by_id(id);
108 	if (prog_fd < 0)
109 		return -1;
110 
111 	if (bpf_obj_get_info_by_fd(prog_fd, &info, &info_len)) {
112 		close(prog_fd);
113 		return -1;
114 	}
115 
116 	attach_type_str = libbpf_bpf_attach_type_str(attach_type);
117 
118 	if (btf_vmlinux) {
119 		if (!btf_vmlinux_id)
120 			guess_vmlinux_btf_id(info.attach_btf_obj_id);
121 
122 		if (btf_vmlinux_id == info.attach_btf_obj_id &&
123 		    info.attach_btf_id < btf__type_cnt(btf_vmlinux)) {
124 			const struct btf_type *t =
125 				btf__type_by_id(btf_vmlinux, info.attach_btf_id);
126 			attach_btf_name =
127 				btf__name_by_offset(btf_vmlinux, t->name_off);
128 		}
129 	}
130 
131 	get_prog_full_name(&info, prog_fd, prog_name, sizeof(prog_name));
132 	if (json_output) {
133 		jsonw_start_object(json_wtr);
134 		jsonw_uint_field(json_wtr, "id", info.id);
135 		if (attach_type_str)
136 			jsonw_string_field(json_wtr, "attach_type", attach_type_str);
137 		else
138 			jsonw_uint_field(json_wtr, "attach_type", attach_type);
139 		jsonw_string_field(json_wtr, "attach_flags",
140 				   attach_flags_str);
141 		jsonw_string_field(json_wtr, "name", prog_name);
142 		if (attach_btf_name)
143 			jsonw_string_field(json_wtr, "attach_btf_name", attach_btf_name);
144 		jsonw_uint_field(json_wtr, "attach_btf_obj_id", info.attach_btf_obj_id);
145 		jsonw_uint_field(json_wtr, "attach_btf_id", info.attach_btf_id);
146 		jsonw_end_object(json_wtr);
147 	} else {
148 		printf("%s%-8u ", level ? "    " : "", info.id);
149 		if (attach_type_str)
150 			printf("%-15s", attach_type_str);
151 		else
152 			printf("type %-10u", attach_type);
153 		printf(" %-15s %-15s", attach_flags_str, prog_name);
154 		if (attach_btf_name)
155 			printf(" %-15s", attach_btf_name);
156 		else if (info.attach_btf_id)
157 			printf(" attach_btf_obj_id=%d attach_btf_id=%d",
158 			       info.attach_btf_obj_id, info.attach_btf_id);
159 		printf("\n");
160 	}
161 
162 	close(prog_fd);
163 	return 0;
164 }
165 
166 static int count_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type)
167 {
168 	__u32 prog_cnt = 0;
169 	int ret;
170 
171 	ret = bpf_prog_query(cgroup_fd, type, query_flags, NULL,
172 			     NULL, &prog_cnt);
173 	if (ret)
174 		return -1;
175 
176 	return prog_cnt;
177 }
178 
179 static int cgroup_has_attached_progs(int cgroup_fd)
180 {
181 	enum bpf_attach_type type;
182 	bool no_prog = true;
183 
184 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
185 		int count = count_attached_bpf_progs(cgroup_fd, type);
186 
187 		if (count < 0 && errno != EINVAL)
188 			return -1;
189 
190 		if (count > 0) {
191 			no_prog = false;
192 			break;
193 		}
194 	}
195 
196 	return no_prog ? 0 : 1;
197 }
198 static int show_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
199 				   int level)
200 {
201 	LIBBPF_OPTS(bpf_prog_query_opts, p);
202 	__u32 prog_attach_flags[1024] = {0};
203 	const char *attach_flags_str;
204 	__u32 prog_ids[1024] = {0};
205 	char buf[32];
206 	__u32 iter;
207 	int ret;
208 
209 	p.query_flags = query_flags;
210 	p.prog_cnt = ARRAY_SIZE(prog_ids);
211 	p.prog_ids = prog_ids;
212 	p.prog_attach_flags = prog_attach_flags;
213 
214 	ret = bpf_prog_query_opts(cgroup_fd, type, &p);
215 	if (ret)
216 		return ret;
217 
218 	if (p.prog_cnt == 0)
219 		return 0;
220 
221 	for (iter = 0; iter < p.prog_cnt; iter++) {
222 		__u32 attach_flags;
223 
224 		attach_flags = prog_attach_flags[iter] ?: p.attach_flags;
225 
226 		switch (attach_flags) {
227 		case BPF_F_ALLOW_MULTI:
228 			attach_flags_str = "multi";
229 			break;
230 		case BPF_F_ALLOW_OVERRIDE:
231 			attach_flags_str = "override";
232 			break;
233 		case 0:
234 			attach_flags_str = "";
235 			break;
236 		default:
237 			snprintf(buf, sizeof(buf), "unknown(%x)", attach_flags);
238 			attach_flags_str = buf;
239 		}
240 
241 		show_bpf_prog(prog_ids[iter], type,
242 			      attach_flags_str, level);
243 	}
244 
245 	return 0;
246 }
247 
248 static int do_show(int argc, char **argv)
249 {
250 	enum bpf_attach_type type;
251 	int has_attached_progs;
252 	const char *path;
253 	int cgroup_fd;
254 	int ret = -1;
255 
256 	query_flags = 0;
257 
258 	if (!REQ_ARGS(1))
259 		return -1;
260 	path = GET_ARG();
261 
262 	while (argc) {
263 		if (is_prefix(*argv, "effective")) {
264 			if (query_flags & BPF_F_QUERY_EFFECTIVE) {
265 				p_err("duplicated argument: %s", *argv);
266 				return -1;
267 			}
268 			query_flags |= BPF_F_QUERY_EFFECTIVE;
269 			NEXT_ARG();
270 		} else {
271 			p_err("expected no more arguments, 'effective', got: '%s'?",
272 			      *argv);
273 			return -1;
274 		}
275 	}
276 
277 	cgroup_fd = open(path, O_RDONLY);
278 	if (cgroup_fd < 0) {
279 		p_err("can't open cgroup %s", path);
280 		goto exit;
281 	}
282 
283 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
284 	if (has_attached_progs < 0) {
285 		p_err("can't query bpf programs attached to %s: %s",
286 		      path, strerror(errno));
287 		goto exit_cgroup;
288 	} else if (!has_attached_progs) {
289 		ret = 0;
290 		goto exit_cgroup;
291 	}
292 
293 	if (json_output)
294 		jsonw_start_array(json_wtr);
295 	else
296 		printf("%-8s %-15s %-15s %-15s\n", "ID", "AttachType",
297 		       "AttachFlags", "Name");
298 
299 	btf_vmlinux = libbpf_find_kernel_btf();
300 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
301 		/*
302 		 * Not all attach types may be supported, so it's expected,
303 		 * that some requests will fail.
304 		 * If we were able to get the show for at least one
305 		 * attach type, let's return 0.
306 		 */
307 		if (show_attached_bpf_progs(cgroup_fd, type, 0) == 0)
308 			ret = 0;
309 	}
310 
311 	if (json_output)
312 		jsonw_end_array(json_wtr);
313 
314 exit_cgroup:
315 	close(cgroup_fd);
316 exit:
317 	return ret;
318 }
319 
320 /*
321  * To distinguish nftw() errors and do_show_tree_fn() errors
322  * and avoid duplicating error messages, let's return -2
323  * from do_show_tree_fn() in case of error.
324  */
325 #define NFTW_ERR		-1
326 #define SHOW_TREE_FN_ERR	-2
327 static int do_show_tree_fn(const char *fpath, const struct stat *sb,
328 			   int typeflag, struct FTW *ftw)
329 {
330 	enum bpf_attach_type type;
331 	int has_attached_progs;
332 	int cgroup_fd;
333 
334 	if (typeflag != FTW_D)
335 		return 0;
336 
337 	cgroup_fd = open(fpath, O_RDONLY);
338 	if (cgroup_fd < 0) {
339 		p_err("can't open cgroup %s: %s", fpath, strerror(errno));
340 		return SHOW_TREE_FN_ERR;
341 	}
342 
343 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
344 	if (has_attached_progs < 0) {
345 		p_err("can't query bpf programs attached to %s: %s",
346 		      fpath, strerror(errno));
347 		close(cgroup_fd);
348 		return SHOW_TREE_FN_ERR;
349 	} else if (!has_attached_progs) {
350 		close(cgroup_fd);
351 		return 0;
352 	}
353 
354 	if (json_output) {
355 		jsonw_start_object(json_wtr);
356 		jsonw_string_field(json_wtr, "cgroup", fpath);
357 		jsonw_name(json_wtr, "programs");
358 		jsonw_start_array(json_wtr);
359 	} else {
360 		printf("%s\n", fpath);
361 	}
362 
363 	btf_vmlinux = libbpf_find_kernel_btf();
364 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++)
365 		show_attached_bpf_progs(cgroup_fd, type, ftw->level);
366 
367 	if (errno == EINVAL)
368 		/* Last attach type does not support query.
369 		 * Do not report an error for this, especially because batch
370 		 * mode would stop processing commands.
371 		 */
372 		errno = 0;
373 
374 	if (json_output) {
375 		jsonw_end_array(json_wtr);
376 		jsonw_end_object(json_wtr);
377 	}
378 
379 	close(cgroup_fd);
380 
381 	return 0;
382 }
383 
384 static char *find_cgroup_root(void)
385 {
386 	struct mntent *mnt;
387 	FILE *f;
388 
389 	f = fopen("/proc/mounts", "r");
390 	if (f == NULL)
391 		return NULL;
392 
393 	while ((mnt = getmntent(f))) {
394 		if (strcmp(mnt->mnt_type, "cgroup2") == 0) {
395 			fclose(f);
396 			return strdup(mnt->mnt_dir);
397 		}
398 	}
399 
400 	fclose(f);
401 	return NULL;
402 }
403 
404 static int do_show_tree(int argc, char **argv)
405 {
406 	char *cgroup_root, *cgroup_alloced = NULL;
407 	int ret;
408 
409 	query_flags = 0;
410 
411 	if (!argc) {
412 		cgroup_alloced = find_cgroup_root();
413 		if (!cgroup_alloced) {
414 			p_err("cgroup v2 isn't mounted");
415 			return -1;
416 		}
417 		cgroup_root = cgroup_alloced;
418 	} else {
419 		cgroup_root = GET_ARG();
420 
421 		while (argc) {
422 			if (is_prefix(*argv, "effective")) {
423 				if (query_flags & BPF_F_QUERY_EFFECTIVE) {
424 					p_err("duplicated argument: %s", *argv);
425 					return -1;
426 				}
427 				query_flags |= BPF_F_QUERY_EFFECTIVE;
428 				NEXT_ARG();
429 			} else {
430 				p_err("expected no more arguments, 'effective', got: '%s'?",
431 				      *argv);
432 				return -1;
433 			}
434 		}
435 	}
436 
437 	if (json_output)
438 		jsonw_start_array(json_wtr);
439 	else
440 		printf("%s\n"
441 		       "%-8s %-15s %-15s %-15s\n",
442 		       "CgroupPath",
443 		       "ID", "AttachType", "AttachFlags", "Name");
444 
445 	switch (nftw(cgroup_root, do_show_tree_fn, 1024, FTW_MOUNT)) {
446 	case NFTW_ERR:
447 		p_err("can't iterate over %s: %s", cgroup_root,
448 		      strerror(errno));
449 		ret = -1;
450 		break;
451 	case SHOW_TREE_FN_ERR:
452 		ret = -1;
453 		break;
454 	default:
455 		ret = 0;
456 	}
457 
458 	if (json_output)
459 		jsonw_end_array(json_wtr);
460 
461 	free(cgroup_alloced);
462 
463 	return ret;
464 }
465 
466 static int do_attach(int argc, char **argv)
467 {
468 	enum bpf_attach_type attach_type;
469 	int cgroup_fd, prog_fd;
470 	int attach_flags = 0;
471 	int ret = -1;
472 	int i;
473 
474 	if (argc < 4) {
475 		p_err("too few parameters for cgroup attach");
476 		goto exit;
477 	}
478 
479 	cgroup_fd = open(argv[0], O_RDONLY);
480 	if (cgroup_fd < 0) {
481 		p_err("can't open cgroup %s", argv[0]);
482 		goto exit;
483 	}
484 
485 	attach_type = parse_attach_type(argv[1]);
486 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
487 		p_err("invalid attach type");
488 		goto exit_cgroup;
489 	}
490 
491 	argc -= 2;
492 	argv = &argv[2];
493 	prog_fd = prog_parse_fd(&argc, &argv);
494 	if (prog_fd < 0)
495 		goto exit_cgroup;
496 
497 	for (i = 0; i < argc; i++) {
498 		if (is_prefix(argv[i], "multi")) {
499 			attach_flags |= BPF_F_ALLOW_MULTI;
500 		} else if (is_prefix(argv[i], "override")) {
501 			attach_flags |= BPF_F_ALLOW_OVERRIDE;
502 		} else {
503 			p_err("unknown option: %s", argv[i]);
504 			goto exit_cgroup;
505 		}
506 	}
507 
508 	if (bpf_prog_attach(prog_fd, cgroup_fd, attach_type, attach_flags)) {
509 		p_err("failed to attach program");
510 		goto exit_prog;
511 	}
512 
513 	if (json_output)
514 		jsonw_null(json_wtr);
515 
516 	ret = 0;
517 
518 exit_prog:
519 	close(prog_fd);
520 exit_cgroup:
521 	close(cgroup_fd);
522 exit:
523 	return ret;
524 }
525 
526 static int do_detach(int argc, char **argv)
527 {
528 	enum bpf_attach_type attach_type;
529 	int prog_fd, cgroup_fd;
530 	int ret = -1;
531 
532 	if (argc < 4) {
533 		p_err("too few parameters for cgroup detach");
534 		goto exit;
535 	}
536 
537 	cgroup_fd = open(argv[0], O_RDONLY);
538 	if (cgroup_fd < 0) {
539 		p_err("can't open cgroup %s", argv[0]);
540 		goto exit;
541 	}
542 
543 	attach_type = parse_attach_type(argv[1]);
544 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
545 		p_err("invalid attach type");
546 		goto exit_cgroup;
547 	}
548 
549 	argc -= 2;
550 	argv = &argv[2];
551 	prog_fd = prog_parse_fd(&argc, &argv);
552 	if (prog_fd < 0)
553 		goto exit_cgroup;
554 
555 	if (bpf_prog_detach2(prog_fd, cgroup_fd, attach_type)) {
556 		p_err("failed to detach program");
557 		goto exit_prog;
558 	}
559 
560 	if (json_output)
561 		jsonw_null(json_wtr);
562 
563 	ret = 0;
564 
565 exit_prog:
566 	close(prog_fd);
567 exit_cgroup:
568 	close(cgroup_fd);
569 exit:
570 	return ret;
571 }
572 
573 static int do_help(int argc, char **argv)
574 {
575 	if (json_output) {
576 		jsonw_null(json_wtr);
577 		return 0;
578 	}
579 
580 	fprintf(stderr,
581 		"Usage: %1$s %2$s { show | list } CGROUP [**effective**]\n"
582 		"       %1$s %2$s tree [CGROUP_ROOT] [**effective**]\n"
583 		"       %1$s %2$s attach CGROUP ATTACH_TYPE PROG [ATTACH_FLAGS]\n"
584 		"       %1$s %2$s detach CGROUP ATTACH_TYPE PROG\n"
585 		"       %1$s %2$s help\n"
586 		"\n"
587 		HELP_SPEC_ATTACH_TYPES "\n"
588 		"       " HELP_SPEC_ATTACH_FLAGS "\n"
589 		"       " HELP_SPEC_PROGRAM "\n"
590 		"       " HELP_SPEC_OPTIONS " |\n"
591 		"                    {-f|--bpffs} }\n"
592 		"",
593 		bin_name, argv[-2]);
594 
595 	return 0;
596 }
597 
598 static const struct cmd cmds[] = {
599 	{ "show",	do_show },
600 	{ "list",	do_show },
601 	{ "tree",       do_show_tree },
602 	{ "attach",	do_attach },
603 	{ "detach",	do_detach },
604 	{ "help",	do_help },
605 	{ 0 }
606 };
607 
608 int do_cgroup(int argc, char **argv)
609 {
610 	return cmd_select(cmds, argc, argv, do_help);
611 }
612