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 
parse_attach_type(const char * str)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 
guess_vmlinux_btf_id(__u32 attach_btf_obj_id)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 
show_bpf_prog(int id,enum bpf_attach_type attach_type,const char * attach_flags_str,int level)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 		if (!(query_flags & BPF_F_QUERY_EFFECTIVE))
140 			jsonw_string_field(json_wtr, "attach_flags", 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 		if (query_flags & BPF_F_QUERY_EFFECTIVE)
154 			printf(" %-15s", prog_name);
155 		else
156 			printf(" %-15s %-15s", attach_flags_str, prog_name);
157 		if (attach_btf_name)
158 			printf(" %-15s", attach_btf_name);
159 		else if (info.attach_btf_id)
160 			printf(" attach_btf_obj_id=%d attach_btf_id=%d",
161 			       info.attach_btf_obj_id, info.attach_btf_id);
162 		printf("\n");
163 	}
164 
165 	close(prog_fd);
166 	return 0;
167 }
168 
count_attached_bpf_progs(int cgroup_fd,enum bpf_attach_type type)169 static int count_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type)
170 {
171 	__u32 prog_cnt = 0;
172 	int ret;
173 
174 	ret = bpf_prog_query(cgroup_fd, type, query_flags, NULL,
175 			     NULL, &prog_cnt);
176 	if (ret)
177 		return -1;
178 
179 	return prog_cnt;
180 }
181 
cgroup_has_attached_progs(int cgroup_fd)182 static int cgroup_has_attached_progs(int cgroup_fd)
183 {
184 	enum bpf_attach_type type;
185 	bool no_prog = true;
186 
187 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
188 		int count = count_attached_bpf_progs(cgroup_fd, type);
189 
190 		if (count < 0 && errno != EINVAL)
191 			return -1;
192 
193 		if (count > 0) {
194 			no_prog = false;
195 			break;
196 		}
197 	}
198 
199 	return no_prog ? 0 : 1;
200 }
201 
show_effective_bpf_progs(int cgroup_fd,enum bpf_attach_type type,int level)202 static int show_effective_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
203 				    int level)
204 {
205 	LIBBPF_OPTS(bpf_prog_query_opts, p);
206 	__u32 prog_ids[1024] = {0};
207 	__u32 iter;
208 	int ret;
209 
210 	p.query_flags = query_flags;
211 	p.prog_cnt = ARRAY_SIZE(prog_ids);
212 	p.prog_ids = prog_ids;
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 		show_bpf_prog(prog_ids[iter], type, NULL, level);
223 
224 	return 0;
225 }
226 
show_attached_bpf_progs(int cgroup_fd,enum bpf_attach_type type,int level)227 static int show_attached_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
228 				   int level)
229 {
230 	LIBBPF_OPTS(bpf_prog_query_opts, p);
231 	__u32 prog_attach_flags[1024] = {0};
232 	const char *attach_flags_str;
233 	__u32 prog_ids[1024] = {0};
234 	char buf[32];
235 	__u32 iter;
236 	int ret;
237 
238 	p.query_flags = query_flags;
239 	p.prog_cnt = ARRAY_SIZE(prog_ids);
240 	p.prog_ids = prog_ids;
241 	p.prog_attach_flags = prog_attach_flags;
242 
243 	ret = bpf_prog_query_opts(cgroup_fd, type, &p);
244 	if (ret)
245 		return ret;
246 
247 	if (p.prog_cnt == 0)
248 		return 0;
249 
250 	for (iter = 0; iter < p.prog_cnt; iter++) {
251 		__u32 attach_flags;
252 
253 		attach_flags = prog_attach_flags[iter] ?: p.attach_flags;
254 
255 		switch (attach_flags) {
256 		case BPF_F_ALLOW_MULTI:
257 			attach_flags_str = "multi";
258 			break;
259 		case BPF_F_ALLOW_OVERRIDE:
260 			attach_flags_str = "override";
261 			break;
262 		case 0:
263 			attach_flags_str = "";
264 			break;
265 		default:
266 			snprintf(buf, sizeof(buf), "unknown(%x)", attach_flags);
267 			attach_flags_str = buf;
268 		}
269 
270 		show_bpf_prog(prog_ids[iter], type,
271 			      attach_flags_str, level);
272 	}
273 
274 	return 0;
275 }
276 
show_bpf_progs(int cgroup_fd,enum bpf_attach_type type,int level)277 static int show_bpf_progs(int cgroup_fd, enum bpf_attach_type type,
278 			  int level)
279 {
280 	return query_flags & BPF_F_QUERY_EFFECTIVE ?
281 	       show_effective_bpf_progs(cgroup_fd, type, level) :
282 	       show_attached_bpf_progs(cgroup_fd, type, level);
283 }
284 
do_show(int argc,char ** argv)285 static int do_show(int argc, char **argv)
286 {
287 	enum bpf_attach_type type;
288 	int has_attached_progs;
289 	const char *path;
290 	int cgroup_fd;
291 	int ret = -1;
292 
293 	query_flags = 0;
294 
295 	if (!REQ_ARGS(1))
296 		return -1;
297 	path = GET_ARG();
298 
299 	while (argc) {
300 		if (is_prefix(*argv, "effective")) {
301 			if (query_flags & BPF_F_QUERY_EFFECTIVE) {
302 				p_err("duplicated argument: %s", *argv);
303 				return -1;
304 			}
305 			query_flags |= BPF_F_QUERY_EFFECTIVE;
306 			NEXT_ARG();
307 		} else {
308 			p_err("expected no more arguments, 'effective', got: '%s'?",
309 			      *argv);
310 			return -1;
311 		}
312 	}
313 
314 	cgroup_fd = open(path, O_RDONLY);
315 	if (cgroup_fd < 0) {
316 		p_err("can't open cgroup %s", path);
317 		goto exit;
318 	}
319 
320 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
321 	if (has_attached_progs < 0) {
322 		p_err("can't query bpf programs attached to %s: %s",
323 		      path, strerror(errno));
324 		goto exit_cgroup;
325 	} else if (!has_attached_progs) {
326 		ret = 0;
327 		goto exit_cgroup;
328 	}
329 
330 	if (json_output)
331 		jsonw_start_array(json_wtr);
332 	else if (query_flags & BPF_F_QUERY_EFFECTIVE)
333 		printf("%-8s %-15s %-15s\n", "ID", "AttachType", "Name");
334 	else
335 		printf("%-8s %-15s %-15s %-15s\n", "ID", "AttachType",
336 		       "AttachFlags", "Name");
337 
338 	btf_vmlinux = libbpf_find_kernel_btf();
339 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
340 		/*
341 		 * Not all attach types may be supported, so it's expected,
342 		 * that some requests will fail.
343 		 * If we were able to get the show for at least one
344 		 * attach type, let's return 0.
345 		 */
346 		if (show_bpf_progs(cgroup_fd, type, 0) == 0)
347 			ret = 0;
348 	}
349 
350 	if (json_output)
351 		jsonw_end_array(json_wtr);
352 
353 exit_cgroup:
354 	close(cgroup_fd);
355 exit:
356 	return ret;
357 }
358 
359 /*
360  * To distinguish nftw() errors and do_show_tree_fn() errors
361  * and avoid duplicating error messages, let's return -2
362  * from do_show_tree_fn() in case of error.
363  */
364 #define NFTW_ERR		-1
365 #define SHOW_TREE_FN_ERR	-2
do_show_tree_fn(const char * fpath,const struct stat * sb,int typeflag,struct FTW * ftw)366 static int do_show_tree_fn(const char *fpath, const struct stat *sb,
367 			   int typeflag, struct FTW *ftw)
368 {
369 	enum bpf_attach_type type;
370 	int has_attached_progs;
371 	int cgroup_fd;
372 
373 	if (typeflag != FTW_D)
374 		return 0;
375 
376 	cgroup_fd = open(fpath, O_RDONLY);
377 	if (cgroup_fd < 0) {
378 		p_err("can't open cgroup %s: %s", fpath, strerror(errno));
379 		return SHOW_TREE_FN_ERR;
380 	}
381 
382 	has_attached_progs = cgroup_has_attached_progs(cgroup_fd);
383 	if (has_attached_progs < 0) {
384 		p_err("can't query bpf programs attached to %s: %s",
385 		      fpath, strerror(errno));
386 		close(cgroup_fd);
387 		return SHOW_TREE_FN_ERR;
388 	} else if (!has_attached_progs) {
389 		close(cgroup_fd);
390 		return 0;
391 	}
392 
393 	if (json_output) {
394 		jsonw_start_object(json_wtr);
395 		jsonw_string_field(json_wtr, "cgroup", fpath);
396 		jsonw_name(json_wtr, "programs");
397 		jsonw_start_array(json_wtr);
398 	} else {
399 		printf("%s\n", fpath);
400 	}
401 
402 	btf_vmlinux = libbpf_find_kernel_btf();
403 	for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++)
404 		show_bpf_progs(cgroup_fd, type, ftw->level);
405 
406 	if (errno == EINVAL)
407 		/* Last attach type does not support query.
408 		 * Do not report an error for this, especially because batch
409 		 * mode would stop processing commands.
410 		 */
411 		errno = 0;
412 
413 	if (json_output) {
414 		jsonw_end_array(json_wtr);
415 		jsonw_end_object(json_wtr);
416 	}
417 
418 	close(cgroup_fd);
419 
420 	return 0;
421 }
422 
find_cgroup_root(void)423 static char *find_cgroup_root(void)
424 {
425 	struct mntent *mnt;
426 	FILE *f;
427 
428 	f = fopen("/proc/mounts", "r");
429 	if (f == NULL)
430 		return NULL;
431 
432 	while ((mnt = getmntent(f))) {
433 		if (strcmp(mnt->mnt_type, "cgroup2") == 0) {
434 			fclose(f);
435 			return strdup(mnt->mnt_dir);
436 		}
437 	}
438 
439 	fclose(f);
440 	return NULL;
441 }
442 
do_show_tree(int argc,char ** argv)443 static int do_show_tree(int argc, char **argv)
444 {
445 	char *cgroup_root, *cgroup_alloced = NULL;
446 	int ret;
447 
448 	query_flags = 0;
449 
450 	if (!argc) {
451 		cgroup_alloced = find_cgroup_root();
452 		if (!cgroup_alloced) {
453 			p_err("cgroup v2 isn't mounted");
454 			return -1;
455 		}
456 		cgroup_root = cgroup_alloced;
457 	} else {
458 		cgroup_root = GET_ARG();
459 
460 		while (argc) {
461 			if (is_prefix(*argv, "effective")) {
462 				if (query_flags & BPF_F_QUERY_EFFECTIVE) {
463 					p_err("duplicated argument: %s", *argv);
464 					return -1;
465 				}
466 				query_flags |= BPF_F_QUERY_EFFECTIVE;
467 				NEXT_ARG();
468 			} else {
469 				p_err("expected no more arguments, 'effective', got: '%s'?",
470 				      *argv);
471 				return -1;
472 			}
473 		}
474 	}
475 
476 	if (json_output)
477 		jsonw_start_array(json_wtr);
478 	else if (query_flags & BPF_F_QUERY_EFFECTIVE)
479 		printf("%s\n"
480 		       "%-8s %-15s %-15s\n",
481 		       "CgroupPath",
482 		       "ID", "AttachType", "Name");
483 	else
484 		printf("%s\n"
485 		       "%-8s %-15s %-15s %-15s\n",
486 		       "CgroupPath",
487 		       "ID", "AttachType", "AttachFlags", "Name");
488 
489 	switch (nftw(cgroup_root, do_show_tree_fn, 1024, FTW_MOUNT)) {
490 	case NFTW_ERR:
491 		p_err("can't iterate over %s: %s", cgroup_root,
492 		      strerror(errno));
493 		ret = -1;
494 		break;
495 	case SHOW_TREE_FN_ERR:
496 		ret = -1;
497 		break;
498 	default:
499 		ret = 0;
500 	}
501 
502 	if (json_output)
503 		jsonw_end_array(json_wtr);
504 
505 	free(cgroup_alloced);
506 
507 	return ret;
508 }
509 
do_attach(int argc,char ** argv)510 static int do_attach(int argc, char **argv)
511 {
512 	enum bpf_attach_type attach_type;
513 	int cgroup_fd, prog_fd;
514 	int attach_flags = 0;
515 	int ret = -1;
516 	int i;
517 
518 	if (argc < 4) {
519 		p_err("too few parameters for cgroup attach");
520 		goto exit;
521 	}
522 
523 	cgroup_fd = open(argv[0], O_RDONLY);
524 	if (cgroup_fd < 0) {
525 		p_err("can't open cgroup %s", argv[0]);
526 		goto exit;
527 	}
528 
529 	attach_type = parse_attach_type(argv[1]);
530 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
531 		p_err("invalid attach type");
532 		goto exit_cgroup;
533 	}
534 
535 	argc -= 2;
536 	argv = &argv[2];
537 	prog_fd = prog_parse_fd(&argc, &argv);
538 	if (prog_fd < 0)
539 		goto exit_cgroup;
540 
541 	for (i = 0; i < argc; i++) {
542 		if (is_prefix(argv[i], "multi")) {
543 			attach_flags |= BPF_F_ALLOW_MULTI;
544 		} else if (is_prefix(argv[i], "override")) {
545 			attach_flags |= BPF_F_ALLOW_OVERRIDE;
546 		} else {
547 			p_err("unknown option: %s", argv[i]);
548 			goto exit_cgroup;
549 		}
550 	}
551 
552 	if (bpf_prog_attach(prog_fd, cgroup_fd, attach_type, attach_flags)) {
553 		p_err("failed to attach program");
554 		goto exit_prog;
555 	}
556 
557 	if (json_output)
558 		jsonw_null(json_wtr);
559 
560 	ret = 0;
561 
562 exit_prog:
563 	close(prog_fd);
564 exit_cgroup:
565 	close(cgroup_fd);
566 exit:
567 	return ret;
568 }
569 
do_detach(int argc,char ** argv)570 static int do_detach(int argc, char **argv)
571 {
572 	enum bpf_attach_type attach_type;
573 	int prog_fd, cgroup_fd;
574 	int ret = -1;
575 
576 	if (argc < 4) {
577 		p_err("too few parameters for cgroup detach");
578 		goto exit;
579 	}
580 
581 	cgroup_fd = open(argv[0], O_RDONLY);
582 	if (cgroup_fd < 0) {
583 		p_err("can't open cgroup %s", argv[0]);
584 		goto exit;
585 	}
586 
587 	attach_type = parse_attach_type(argv[1]);
588 	if (attach_type == __MAX_BPF_ATTACH_TYPE) {
589 		p_err("invalid attach type");
590 		goto exit_cgroup;
591 	}
592 
593 	argc -= 2;
594 	argv = &argv[2];
595 	prog_fd = prog_parse_fd(&argc, &argv);
596 	if (prog_fd < 0)
597 		goto exit_cgroup;
598 
599 	if (bpf_prog_detach2(prog_fd, cgroup_fd, attach_type)) {
600 		p_err("failed to detach program");
601 		goto exit_prog;
602 	}
603 
604 	if (json_output)
605 		jsonw_null(json_wtr);
606 
607 	ret = 0;
608 
609 exit_prog:
610 	close(prog_fd);
611 exit_cgroup:
612 	close(cgroup_fd);
613 exit:
614 	return ret;
615 }
616 
do_help(int argc,char ** argv)617 static int do_help(int argc, char **argv)
618 {
619 	if (json_output) {
620 		jsonw_null(json_wtr);
621 		return 0;
622 	}
623 
624 	fprintf(stderr,
625 		"Usage: %1$s %2$s { show | list } CGROUP [**effective**]\n"
626 		"       %1$s %2$s tree [CGROUP_ROOT] [**effective**]\n"
627 		"       %1$s %2$s attach CGROUP ATTACH_TYPE PROG [ATTACH_FLAGS]\n"
628 		"       %1$s %2$s detach CGROUP ATTACH_TYPE PROG\n"
629 		"       %1$s %2$s help\n"
630 		"\n"
631 		HELP_SPEC_ATTACH_TYPES "\n"
632 		"       " HELP_SPEC_ATTACH_FLAGS "\n"
633 		"       " HELP_SPEC_PROGRAM "\n"
634 		"       " HELP_SPEC_OPTIONS " |\n"
635 		"                    {-f|--bpffs} }\n"
636 		"",
637 		bin_name, argv[-2]);
638 
639 	return 0;
640 }
641 
642 static const struct cmd cmds[] = {
643 	{ "show",	do_show },
644 	{ "list",	do_show },
645 	{ "tree",       do_show_tree },
646 	{ "attach",	do_attach },
647 	{ "detach",	do_detach },
648 	{ "help",	do_help },
649 	{ 0 }
650 };
651 
do_cgroup(int argc,char ** argv)652 int do_cgroup(int argc, char **argv)
653 {
654 	return cmd_select(cmds, argc, argv, do_help);
655 }
656