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