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