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