1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (c) 2019 Netronome Systems, Inc. */ |
3 | |
4 | #include <ctype.h> |
5 | #include <errno.h> |
6 | #include <fcntl.h> |
7 | #include <string.h> |
8 | #include <unistd.h> |
9 | #include <net/if.h> |
10 | #ifdef USE_LIBCAP |
11 | #include <sys/capability.h> |
12 | #endif |
13 | #include <sys/utsname.h> |
14 | #include <sys/vfs.h> |
15 | |
16 | #include <linux/filter.h> |
17 | #include <linux/limits.h> |
18 | |
19 | #include <bpf/bpf.h> |
20 | #include <bpf/libbpf.h> |
21 | #include <zlib.h> |
22 | |
23 | #include "main.h" |
24 | |
25 | #ifndef PROC_SUPER_MAGIC |
26 | # define PROC_SUPER_MAGIC 0x9fa0 |
27 | #endif |
28 | |
29 | enum probe_component { |
30 | COMPONENT_UNSPEC, |
31 | COMPONENT_KERNEL, |
32 | COMPONENT_DEVICE, |
33 | }; |
34 | |
35 | #define BPF_HELPER_MAKE_ENTRY(name) [BPF_FUNC_ ## name] = "bpf_" # name |
36 | static const char * const helper_name[] = { |
37 | __BPF_FUNC_MAPPER(BPF_HELPER_MAKE_ENTRY) |
38 | }; |
39 | |
40 | #undef BPF_HELPER_MAKE_ENTRY |
41 | |
42 | static bool full_mode; |
43 | #ifdef USE_LIBCAP |
44 | static bool run_as_unprivileged; |
45 | #endif |
46 | |
47 | /* Miscellaneous utility functions */ |
48 | |
49 | static bool grep(const char *buffer, const char *pattern) |
50 | { |
51 | return !!strstr(buffer, pattern); |
52 | } |
53 | |
54 | static bool check_procfs(void) |
55 | { |
56 | struct statfs st_fs; |
57 | |
58 | if (statfs("/proc" , &st_fs) < 0) |
59 | return false; |
60 | if ((unsigned long)st_fs.f_type != PROC_SUPER_MAGIC) |
61 | return false; |
62 | |
63 | return true; |
64 | } |
65 | |
66 | static void uppercase(char *str, size_t len) |
67 | { |
68 | size_t i; |
69 | |
70 | for (i = 0; i < len && str[i] != '\0'; i++) |
71 | str[i] = toupper(str[i]); |
72 | } |
73 | |
74 | /* Printing utility functions */ |
75 | |
76 | static void |
77 | print_bool_feature(const char *feat_name, const char *plain_name, |
78 | const char *define_name, bool res, const char *define_prefix) |
79 | { |
80 | if (json_output) |
81 | jsonw_bool_field(self: json_wtr, prop: feat_name, value: res); |
82 | else if (define_prefix) |
83 | printf("#define %s%sHAVE_%s\n" , define_prefix, |
84 | res ? "" : "NO_" , define_name); |
85 | else |
86 | printf("%s is %savailable\n" , plain_name, res ? "" : "NOT " ); |
87 | } |
88 | |
89 | static void print_kernel_option(const char *name, const char *value, |
90 | const char *define_prefix) |
91 | { |
92 | char *endptr; |
93 | int res; |
94 | |
95 | if (json_output) { |
96 | if (!value) { |
97 | jsonw_null_field(self: json_wtr, prop: name); |
98 | return; |
99 | } |
100 | errno = 0; |
101 | res = strtol(value, &endptr, 0); |
102 | if (!errno && *endptr == '\n') |
103 | jsonw_int_field(self: json_wtr, prop: name, num: res); |
104 | else |
105 | jsonw_string_field(self: json_wtr, prop: name, val: value); |
106 | } else if (define_prefix) { |
107 | if (value) |
108 | printf("#define %s%s %s\n" , define_prefix, |
109 | name, value); |
110 | else |
111 | printf("/* %s%s is not set */\n" , define_prefix, name); |
112 | } else { |
113 | if (value) |
114 | printf("%s is set to %s\n" , name, value); |
115 | else |
116 | printf("%s is not set\n" , name); |
117 | } |
118 | } |
119 | |
120 | static void |
121 | print_start_section(const char *json_title, const char *plain_title, |
122 | const char *, const char *define_prefix) |
123 | { |
124 | if (json_output) { |
125 | jsonw_name(self: json_wtr, name: json_title); |
126 | jsonw_start_object(self: json_wtr); |
127 | } else if (define_prefix) { |
128 | printf("%s\n" , define_comment); |
129 | } else { |
130 | printf("%s\n" , plain_title); |
131 | } |
132 | } |
133 | |
134 | static void print_end_section(void) |
135 | { |
136 | if (json_output) |
137 | jsonw_end_object(self: json_wtr); |
138 | else |
139 | printf("\n" ); |
140 | } |
141 | |
142 | /* Probing functions */ |
143 | |
144 | static int get_vendor_id(int ifindex) |
145 | { |
146 | char ifname[IF_NAMESIZE], path[64], buf[8]; |
147 | ssize_t len; |
148 | int fd; |
149 | |
150 | if (!if_indextoname(ifindex, ifname)) |
151 | return -1; |
152 | |
153 | snprintf(path, sizeof(path), "/sys/class/net/%s/device/vendor" , ifname); |
154 | |
155 | fd = open(path, O_RDONLY | O_CLOEXEC); |
156 | if (fd < 0) |
157 | return -1; |
158 | |
159 | len = read(fd, buf, sizeof(buf)); |
160 | close(fd); |
161 | if (len < 0) |
162 | return -1; |
163 | if (len >= (ssize_t)sizeof(buf)) |
164 | return -1; |
165 | buf[len] = '\0'; |
166 | |
167 | return strtol(buf, NULL, 0); |
168 | } |
169 | |
170 | static long read_procfs(const char *path) |
171 | { |
172 | char *endptr, *line = NULL; |
173 | size_t len = 0; |
174 | FILE *fd; |
175 | long res; |
176 | |
177 | fd = fopen(path, "r" ); |
178 | if (!fd) |
179 | return -1; |
180 | |
181 | res = getline(&line, &len, fd); |
182 | fclose(fd); |
183 | if (res < 0) |
184 | return -1; |
185 | |
186 | errno = 0; |
187 | res = strtol(line, &endptr, 10); |
188 | if (errno || *line == '\0' || *endptr != '\n') |
189 | res = -1; |
190 | free(line); |
191 | |
192 | return res; |
193 | } |
194 | |
195 | static void probe_unprivileged_disabled(void) |
196 | { |
197 | long res; |
198 | |
199 | /* No support for C-style ouptut */ |
200 | |
201 | res = read_procfs(path: "/proc/sys/kernel/unprivileged_bpf_disabled" ); |
202 | if (json_output) { |
203 | jsonw_int_field(self: json_wtr, prop: "unprivileged_bpf_disabled" , num: res); |
204 | } else { |
205 | switch (res) { |
206 | case 0: |
207 | printf("bpf() syscall for unprivileged users is enabled\n" ); |
208 | break; |
209 | case 1: |
210 | printf("bpf() syscall restricted to privileged users (without recovery)\n" ); |
211 | break; |
212 | case 2: |
213 | printf("bpf() syscall restricted to privileged users (admin can change)\n" ); |
214 | break; |
215 | case -1: |
216 | printf("Unable to retrieve required privileges for bpf() syscall\n" ); |
217 | break; |
218 | default: |
219 | printf("bpf() syscall restriction has unknown value %ld\n" , res); |
220 | } |
221 | } |
222 | } |
223 | |
224 | static void probe_jit_enable(void) |
225 | { |
226 | long res; |
227 | |
228 | /* No support for C-style ouptut */ |
229 | |
230 | res = read_procfs(path: "/proc/sys/net/core/bpf_jit_enable" ); |
231 | if (json_output) { |
232 | jsonw_int_field(self: json_wtr, prop: "bpf_jit_enable" , num: res); |
233 | } else { |
234 | switch (res) { |
235 | case 0: |
236 | printf("JIT compiler is disabled\n" ); |
237 | break; |
238 | case 1: |
239 | printf("JIT compiler is enabled\n" ); |
240 | break; |
241 | case 2: |
242 | printf("JIT compiler is enabled with debugging traces in kernel logs\n" ); |
243 | break; |
244 | case -1: |
245 | printf("Unable to retrieve JIT-compiler status\n" ); |
246 | break; |
247 | default: |
248 | printf("JIT-compiler status has unknown value %ld\n" , |
249 | res); |
250 | } |
251 | } |
252 | } |
253 | |
254 | static void probe_jit_harden(void) |
255 | { |
256 | long res; |
257 | |
258 | /* No support for C-style ouptut */ |
259 | |
260 | res = read_procfs(path: "/proc/sys/net/core/bpf_jit_harden" ); |
261 | if (json_output) { |
262 | jsonw_int_field(self: json_wtr, prop: "bpf_jit_harden" , num: res); |
263 | } else { |
264 | switch (res) { |
265 | case 0: |
266 | printf("JIT compiler hardening is disabled\n" ); |
267 | break; |
268 | case 1: |
269 | printf("JIT compiler hardening is enabled for unprivileged users\n" ); |
270 | break; |
271 | case 2: |
272 | printf("JIT compiler hardening is enabled for all users\n" ); |
273 | break; |
274 | case -1: |
275 | printf("Unable to retrieve JIT hardening status\n" ); |
276 | break; |
277 | default: |
278 | printf("JIT hardening status has unknown value %ld\n" , |
279 | res); |
280 | } |
281 | } |
282 | } |
283 | |
284 | static void probe_jit_kallsyms(void) |
285 | { |
286 | long res; |
287 | |
288 | /* No support for C-style ouptut */ |
289 | |
290 | res = read_procfs(path: "/proc/sys/net/core/bpf_jit_kallsyms" ); |
291 | if (json_output) { |
292 | jsonw_int_field(self: json_wtr, prop: "bpf_jit_kallsyms" , num: res); |
293 | } else { |
294 | switch (res) { |
295 | case 0: |
296 | printf("JIT compiler kallsyms exports are disabled\n" ); |
297 | break; |
298 | case 1: |
299 | printf("JIT compiler kallsyms exports are enabled for root\n" ); |
300 | break; |
301 | case -1: |
302 | printf("Unable to retrieve JIT kallsyms export status\n" ); |
303 | break; |
304 | default: |
305 | printf("JIT kallsyms exports status has unknown value %ld\n" , res); |
306 | } |
307 | } |
308 | } |
309 | |
310 | static void probe_jit_limit(void) |
311 | { |
312 | long res; |
313 | |
314 | /* No support for C-style ouptut */ |
315 | |
316 | res = read_procfs(path: "/proc/sys/net/core/bpf_jit_limit" ); |
317 | if (json_output) { |
318 | jsonw_int_field(self: json_wtr, prop: "bpf_jit_limit" , num: res); |
319 | } else { |
320 | switch (res) { |
321 | case -1: |
322 | printf("Unable to retrieve global memory limit for JIT compiler for unprivileged users\n" ); |
323 | break; |
324 | default: |
325 | printf("Global memory limit for JIT compiler for unprivileged users is %ld bytes\n" , res); |
326 | } |
327 | } |
328 | } |
329 | |
330 | static bool read_next_kernel_config_option(gzFile file, char *buf, size_t n, |
331 | char **value) |
332 | { |
333 | char *sep; |
334 | |
335 | while (gzgets(file, buf, n)) { |
336 | if (strncmp(buf, "CONFIG_" , 7)) |
337 | continue; |
338 | |
339 | sep = strchr(buf, '='); |
340 | if (!sep) |
341 | continue; |
342 | |
343 | /* Trim ending '\n' */ |
344 | buf[strlen(buf) - 1] = '\0'; |
345 | |
346 | /* Split on '=' and ensure that a value is present. */ |
347 | *sep = '\0'; |
348 | if (!sep[1]) |
349 | continue; |
350 | |
351 | *value = sep + 1; |
352 | return true; |
353 | } |
354 | |
355 | return false; |
356 | } |
357 | |
358 | static void probe_kernel_image_config(const char *define_prefix) |
359 | { |
360 | static const struct { |
361 | const char * const name; |
362 | bool macro_dump; |
363 | } options[] = { |
364 | /* Enable BPF */ |
365 | { "CONFIG_BPF" , }, |
366 | /* Enable bpf() syscall */ |
367 | { "CONFIG_BPF_SYSCALL" , }, |
368 | /* Does selected architecture support eBPF JIT compiler */ |
369 | { "CONFIG_HAVE_EBPF_JIT" , }, |
370 | /* Compile eBPF JIT compiler */ |
371 | { "CONFIG_BPF_JIT" , }, |
372 | /* Avoid compiling eBPF interpreter (use JIT only) */ |
373 | { "CONFIG_BPF_JIT_ALWAYS_ON" , }, |
374 | /* Kernel BTF debug information available */ |
375 | { "CONFIG_DEBUG_INFO_BTF" , }, |
376 | /* Kernel module BTF debug information available */ |
377 | { "CONFIG_DEBUG_INFO_BTF_MODULES" , }, |
378 | |
379 | /* cgroups */ |
380 | { "CONFIG_CGROUPS" , }, |
381 | /* BPF programs attached to cgroups */ |
382 | { "CONFIG_CGROUP_BPF" , }, |
383 | /* bpf_get_cgroup_classid() helper */ |
384 | { "CONFIG_CGROUP_NET_CLASSID" , }, |
385 | /* bpf_skb_{,ancestor_}cgroup_id() helpers */ |
386 | { "CONFIG_SOCK_CGROUP_DATA" , }, |
387 | |
388 | /* Tracing: attach BPF to kprobes, tracepoints, etc. */ |
389 | { "CONFIG_BPF_EVENTS" , }, |
390 | /* Kprobes */ |
391 | { "CONFIG_KPROBE_EVENTS" , }, |
392 | /* Uprobes */ |
393 | { "CONFIG_UPROBE_EVENTS" , }, |
394 | /* Tracepoints */ |
395 | { "CONFIG_TRACING" , }, |
396 | /* Syscall tracepoints */ |
397 | { "CONFIG_FTRACE_SYSCALLS" , }, |
398 | /* bpf_override_return() helper support for selected arch */ |
399 | { "CONFIG_FUNCTION_ERROR_INJECTION" , }, |
400 | /* bpf_override_return() helper */ |
401 | { "CONFIG_BPF_KPROBE_OVERRIDE" , }, |
402 | |
403 | /* Network */ |
404 | { "CONFIG_NET" , }, |
405 | /* AF_XDP sockets */ |
406 | { "CONFIG_XDP_SOCKETS" , }, |
407 | /* BPF_PROG_TYPE_LWT_* and related helpers */ |
408 | { "CONFIG_LWTUNNEL_BPF" , }, |
409 | /* BPF_PROG_TYPE_SCHED_ACT, TC (traffic control) actions */ |
410 | { "CONFIG_NET_ACT_BPF" , }, |
411 | /* BPF_PROG_TYPE_SCHED_CLS, TC filters */ |
412 | { "CONFIG_NET_CLS_BPF" , }, |
413 | /* TC clsact qdisc */ |
414 | { "CONFIG_NET_CLS_ACT" , }, |
415 | /* Ingress filtering with TC */ |
416 | { "CONFIG_NET_SCH_INGRESS" , }, |
417 | /* bpf_skb_get_xfrm_state() helper */ |
418 | { "CONFIG_XFRM" , }, |
419 | /* bpf_get_route_realm() helper */ |
420 | { "CONFIG_IP_ROUTE_CLASSID" , }, |
421 | /* BPF_PROG_TYPE_LWT_SEG6_LOCAL and related helpers */ |
422 | { "CONFIG_IPV6_SEG6_BPF" , }, |
423 | /* BPF_PROG_TYPE_LIRC_MODE2 and related helpers */ |
424 | { "CONFIG_BPF_LIRC_MODE2" , }, |
425 | /* BPF stream parser and BPF socket maps */ |
426 | { "CONFIG_BPF_STREAM_PARSER" , }, |
427 | /* xt_bpf module for passing BPF programs to netfilter */ |
428 | { "CONFIG_NETFILTER_XT_MATCH_BPF" , }, |
429 | /* bpfilter back-end for iptables */ |
430 | { "CONFIG_BPFILTER" , }, |
431 | /* bpftilter module with "user mode helper" */ |
432 | { "CONFIG_BPFILTER_UMH" , }, |
433 | |
434 | /* test_bpf module for BPF tests */ |
435 | { "CONFIG_TEST_BPF" , }, |
436 | |
437 | /* Misc configs useful in BPF C programs */ |
438 | /* jiffies <-> sec conversion for bpf_jiffies64() helper */ |
439 | { "CONFIG_HZ" , true, } |
440 | }; |
441 | char *values[ARRAY_SIZE(options)] = { }; |
442 | struct utsname utsn; |
443 | char path[PATH_MAX]; |
444 | gzFile file = NULL; |
445 | char buf[4096]; |
446 | char *value; |
447 | size_t i; |
448 | |
449 | if (!uname(&utsn)) { |
450 | snprintf(buf: path, size: sizeof(path), fmt: "/boot/config-%s" , utsn.release); |
451 | |
452 | /* gzopen also accepts uncompressed files. */ |
453 | file = gzopen(path, "r" ); |
454 | } |
455 | |
456 | if (!file) { |
457 | /* Some distributions build with CONFIG_IKCONFIG=y and put the |
458 | * config file at /proc/config.gz. |
459 | */ |
460 | file = gzopen("/proc/config.gz" , "r" ); |
461 | } |
462 | if (!file) { |
463 | p_info(fmt: "skipping kernel config, can't open file: %s" , |
464 | strerror(errno)); |
465 | goto end_parse; |
466 | } |
467 | /* Sanity checks */ |
468 | if (!gzgets(file, buf, sizeof(buf)) || |
469 | !gzgets(file, buf, sizeof(buf))) { |
470 | p_info(fmt: "skipping kernel config, can't read from file: %s" , |
471 | strerror(errno)); |
472 | goto end_parse; |
473 | } |
474 | if (strcmp(buf, "# Automatically generated file; DO NOT EDIT.\n" )) { |
475 | p_info(fmt: "skipping kernel config, can't find correct file" ); |
476 | goto end_parse; |
477 | } |
478 | |
479 | while (read_next_kernel_config_option(file, buf, sizeof(buf), &value)) { |
480 | for (i = 0; i < ARRAY_SIZE(options); i++) { |
481 | if ((define_prefix && !options[i].macro_dump) || |
482 | values[i] || strcmp(buf, options[i].name)) |
483 | continue; |
484 | |
485 | values[i] = strdup(value); |
486 | } |
487 | } |
488 | |
489 | for (i = 0; i < ARRAY_SIZE(options); i++) { |
490 | if (define_prefix && !options[i].macro_dump) |
491 | continue; |
492 | print_kernel_option(name: options[i].name, value: values[i], define_prefix); |
493 | free(values[i]); |
494 | } |
495 | |
496 | end_parse: |
497 | if (file) |
498 | gzclose(file); |
499 | } |
500 | |
501 | static bool probe_bpf_syscall(const char *define_prefix) |
502 | { |
503 | bool res; |
504 | |
505 | bpf_prog_load(BPF_PROG_TYPE_UNSPEC, NULL, NULL, NULL, 0, NULL); |
506 | res = (errno != ENOSYS); |
507 | |
508 | print_bool_feature(feat_name: "have_bpf_syscall" , |
509 | plain_name: "bpf() syscall" , |
510 | define_name: "BPF_SYSCALL" , |
511 | res, define_prefix); |
512 | |
513 | return res; |
514 | } |
515 | |
516 | static bool |
517 | probe_prog_load_ifindex(enum bpf_prog_type prog_type, |
518 | const struct bpf_insn *insns, size_t insns_cnt, |
519 | char *log_buf, size_t log_buf_sz, |
520 | __u32 ifindex) |
521 | { |
522 | LIBBPF_OPTS(bpf_prog_load_opts, opts, |
523 | .log_buf = log_buf, |
524 | .log_size = log_buf_sz, |
525 | .log_level = log_buf ? 1 : 0, |
526 | .prog_ifindex = ifindex, |
527 | ); |
528 | int fd; |
529 | |
530 | errno = 0; |
531 | fd = bpf_prog_load(prog_type, NULL, "GPL" , insns, insns_cnt, &opts); |
532 | if (fd >= 0) |
533 | close(fd); |
534 | |
535 | return fd >= 0 && errno != EINVAL && errno != EOPNOTSUPP; |
536 | } |
537 | |
538 | static bool probe_prog_type_ifindex(enum bpf_prog_type prog_type, __u32 ifindex) |
539 | { |
540 | /* nfp returns -EINVAL on exit(0) with TC offload */ |
541 | struct bpf_insn insns[2] = { |
542 | BPF_MOV64_IMM(BPF_REG_0, 2), |
543 | BPF_EXIT_INSN() |
544 | }; |
545 | |
546 | return probe_prog_load_ifindex(prog_type, insns, ARRAY_SIZE(insns), |
547 | NULL, log_buf_sz: 0, ifindex); |
548 | } |
549 | |
550 | static void |
551 | probe_prog_type(enum bpf_prog_type prog_type, const char *prog_type_str, |
552 | bool *supported_types, const char *define_prefix, __u32 ifindex) |
553 | { |
554 | char feat_name[128], plain_desc[128], define_name[128]; |
555 | const char * = "eBPF program_type " ; |
556 | size_t maxlen; |
557 | bool res; |
558 | |
559 | if (ifindex) { |
560 | switch (prog_type) { |
561 | case BPF_PROG_TYPE_SCHED_CLS: |
562 | case BPF_PROG_TYPE_XDP: |
563 | break; |
564 | default: |
565 | return; |
566 | } |
567 | |
568 | res = probe_prog_type_ifindex(prog_type, ifindex); |
569 | } else { |
570 | res = libbpf_probe_bpf_prog_type(prog_type, NULL) > 0; |
571 | } |
572 | |
573 | #ifdef USE_LIBCAP |
574 | /* Probe may succeed even if program load fails, for unprivileged users |
575 | * check that we did not fail because of insufficient permissions |
576 | */ |
577 | if (run_as_unprivileged && errno == EPERM) |
578 | res = false; |
579 | #endif |
580 | |
581 | supported_types[prog_type] |= res; |
582 | |
583 | maxlen = sizeof(plain_desc) - strlen(plain_comment) - 1; |
584 | if (strlen(prog_type_str) > maxlen) { |
585 | p_info(fmt: "program type name too long" ); |
586 | return; |
587 | } |
588 | |
589 | sprintf(buf: feat_name, fmt: "have_%s_prog_type" , prog_type_str); |
590 | sprintf(buf: define_name, fmt: "%s_prog_type" , prog_type_str); |
591 | uppercase(str: define_name, len: sizeof(define_name)); |
592 | sprintf(buf: plain_desc, fmt: "%s%s" , plain_comment, prog_type_str); |
593 | print_bool_feature(feat_name, plain_name: plain_desc, define_name, res, |
594 | define_prefix); |
595 | } |
596 | |
597 | static bool probe_map_type_ifindex(enum bpf_map_type map_type, __u32 ifindex) |
598 | { |
599 | LIBBPF_OPTS(bpf_map_create_opts, opts); |
600 | int key_size, value_size, max_entries; |
601 | int fd; |
602 | |
603 | opts.map_ifindex = ifindex; |
604 | |
605 | key_size = sizeof(__u32); |
606 | value_size = sizeof(__u32); |
607 | max_entries = 1; |
608 | |
609 | fd = bpf_map_create(map_type, NULL, key_size, value_size, max_entries, |
610 | &opts); |
611 | if (fd >= 0) |
612 | close(fd); |
613 | |
614 | return fd >= 0; |
615 | } |
616 | |
617 | static void |
618 | probe_map_type(enum bpf_map_type map_type, char const *map_type_str, |
619 | const char *define_prefix, __u32 ifindex) |
620 | { |
621 | char feat_name[128], plain_desc[128], define_name[128]; |
622 | const char * = "eBPF map_type " ; |
623 | size_t maxlen; |
624 | bool res; |
625 | |
626 | if (ifindex) { |
627 | switch (map_type) { |
628 | case BPF_MAP_TYPE_HASH: |
629 | case BPF_MAP_TYPE_ARRAY: |
630 | break; |
631 | default: |
632 | return; |
633 | } |
634 | |
635 | res = probe_map_type_ifindex(map_type, ifindex); |
636 | } else { |
637 | res = libbpf_probe_bpf_map_type(map_type, NULL) > 0; |
638 | } |
639 | |
640 | /* Probe result depends on the success of map creation, no additional |
641 | * check required for unprivileged users |
642 | */ |
643 | |
644 | maxlen = sizeof(plain_desc) - strlen(plain_comment) - 1; |
645 | if (strlen(map_type_str) > maxlen) { |
646 | p_info(fmt: "map type name too long" ); |
647 | return; |
648 | } |
649 | |
650 | sprintf(buf: feat_name, fmt: "have_%s_map_type" , map_type_str); |
651 | sprintf(buf: define_name, fmt: "%s_map_type" , map_type_str); |
652 | uppercase(str: define_name, len: sizeof(define_name)); |
653 | sprintf(buf: plain_desc, fmt: "%s%s" , plain_comment, map_type_str); |
654 | print_bool_feature(feat_name, plain_name: plain_desc, define_name, res, |
655 | define_prefix); |
656 | } |
657 | |
658 | static bool |
659 | probe_helper_ifindex(enum bpf_func_id id, enum bpf_prog_type prog_type, |
660 | __u32 ifindex) |
661 | { |
662 | struct bpf_insn insns[2] = { |
663 | BPF_EMIT_CALL(id), |
664 | BPF_EXIT_INSN() |
665 | }; |
666 | char buf[4096] = {}; |
667 | bool res; |
668 | |
669 | probe_prog_load_ifindex(prog_type, insns, ARRAY_SIZE(insns), log_buf: buf, |
670 | log_buf_sz: sizeof(buf), ifindex); |
671 | res = !grep(buffer: buf, pattern: "invalid func " ) && !grep(buffer: buf, pattern: "unknown func " ); |
672 | |
673 | switch (get_vendor_id(ifindex)) { |
674 | case 0x19ee: /* Netronome specific */ |
675 | res = res && !grep(buffer: buf, pattern: "not supported by FW" ) && |
676 | !grep(buffer: buf, pattern: "unsupported function id" ); |
677 | break; |
678 | default: |
679 | break; |
680 | } |
681 | |
682 | return res; |
683 | } |
684 | |
685 | static bool |
686 | probe_helper_for_progtype(enum bpf_prog_type prog_type, bool supported_type, |
687 | const char *define_prefix, unsigned int id, |
688 | const char *ptype_name, __u32 ifindex) |
689 | { |
690 | bool res = false; |
691 | |
692 | if (supported_type) { |
693 | if (ifindex) |
694 | res = probe_helper_ifindex(id, prog_type, ifindex); |
695 | else |
696 | res = libbpf_probe_bpf_helper(prog_type, id, NULL) > 0; |
697 | #ifdef USE_LIBCAP |
698 | /* Probe may succeed even if program load fails, for |
699 | * unprivileged users check that we did not fail because of |
700 | * insufficient permissions |
701 | */ |
702 | if (run_as_unprivileged && errno == EPERM) |
703 | res = false; |
704 | #endif |
705 | } |
706 | |
707 | if (json_output) { |
708 | if (res) |
709 | jsonw_string(self: json_wtr, value: helper_name[id]); |
710 | } else if (define_prefix) { |
711 | printf("#define %sBPF__PROG_TYPE_%s__HELPER_%s %s\n" , |
712 | define_prefix, ptype_name, helper_name[id], |
713 | res ? "1" : "0" ); |
714 | } else { |
715 | if (res) |
716 | printf("\n\t- %s" , helper_name[id]); |
717 | } |
718 | |
719 | return res; |
720 | } |
721 | |
722 | static void |
723 | probe_helpers_for_progtype(enum bpf_prog_type prog_type, |
724 | const char *prog_type_str, bool supported_type, |
725 | const char *define_prefix, __u32 ifindex) |
726 | { |
727 | char feat_name[128]; |
728 | unsigned int id; |
729 | bool probe_res = false; |
730 | |
731 | if (ifindex) |
732 | /* Only test helpers for offload-able program types */ |
733 | switch (prog_type) { |
734 | case BPF_PROG_TYPE_SCHED_CLS: |
735 | case BPF_PROG_TYPE_XDP: |
736 | break; |
737 | default: |
738 | return; |
739 | } |
740 | |
741 | if (json_output) { |
742 | sprintf(buf: feat_name, fmt: "%s_available_helpers" , prog_type_str); |
743 | jsonw_name(self: json_wtr, name: feat_name); |
744 | jsonw_start_array(self: json_wtr); |
745 | } else if (!define_prefix) { |
746 | printf("eBPF helpers supported for program type %s:" , |
747 | prog_type_str); |
748 | } |
749 | |
750 | for (id = 1; id < ARRAY_SIZE(helper_name); id++) { |
751 | /* Skip helper functions which emit dmesg messages when not in |
752 | * the full mode. |
753 | */ |
754 | switch (id) { |
755 | case BPF_FUNC_trace_printk: |
756 | case BPF_FUNC_trace_vprintk: |
757 | case BPF_FUNC_probe_write_user: |
758 | if (!full_mode) |
759 | continue; |
760 | fallthrough; |
761 | default: |
762 | probe_res |= probe_helper_for_progtype(prog_type, supported_type, |
763 | define_prefix, id, ptype_name: prog_type_str, |
764 | ifindex); |
765 | } |
766 | } |
767 | |
768 | if (json_output) |
769 | jsonw_end_array(self: json_wtr); |
770 | else if (!define_prefix) { |
771 | printf("\n" ); |
772 | if (!probe_res) { |
773 | if (!supported_type) |
774 | printf("\tProgram type not supported\n" ); |
775 | else |
776 | printf("\tCould not determine which helpers are available\n" ); |
777 | } |
778 | } |
779 | |
780 | |
781 | } |
782 | |
783 | static void |
784 | probe_misc_feature(struct bpf_insn *insns, size_t len, |
785 | const char *define_prefix, __u32 ifindex, |
786 | const char *feat_name, const char *plain_name, |
787 | const char *define_name) |
788 | { |
789 | LIBBPF_OPTS(bpf_prog_load_opts, opts, |
790 | .prog_ifindex = ifindex, |
791 | ); |
792 | bool res; |
793 | int fd; |
794 | |
795 | errno = 0; |
796 | fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL" , |
797 | insns, len, &opts); |
798 | res = fd >= 0 || !errno; |
799 | |
800 | if (fd >= 0) |
801 | close(fd); |
802 | |
803 | print_bool_feature(feat_name, plain_name, define_name, res, |
804 | define_prefix); |
805 | } |
806 | |
807 | /* |
808 | * Probe for availability of kernel commit (5.3): |
809 | * |
810 | * c04c0d2b968a ("bpf: increase complexity limit and maximum program size") |
811 | */ |
812 | static void probe_large_insn_limit(const char *define_prefix, __u32 ifindex) |
813 | { |
814 | struct bpf_insn insns[BPF_MAXINSNS + 1]; |
815 | int i; |
816 | |
817 | for (i = 0; i < BPF_MAXINSNS; i++) |
818 | insns[i] = BPF_MOV64_IMM(BPF_REG_0, 1); |
819 | insns[BPF_MAXINSNS] = BPF_EXIT_INSN(); |
820 | |
821 | probe_misc_feature(insns, ARRAY_SIZE(insns), |
822 | define_prefix, ifindex, |
823 | feat_name: "have_large_insn_limit" , |
824 | plain_name: "Large program size limit" , |
825 | define_name: "LARGE_INSN_LIMIT" ); |
826 | } |
827 | |
828 | /* |
829 | * Probe for bounded loop support introduced in commit 2589726d12a1 |
830 | * ("bpf: introduce bounded loops"). |
831 | */ |
832 | static void |
833 | probe_bounded_loops(const char *define_prefix, __u32 ifindex) |
834 | { |
835 | struct bpf_insn insns[4] = { |
836 | BPF_MOV64_IMM(BPF_REG_0, 10), |
837 | BPF_ALU64_IMM(BPF_SUB, BPF_REG_0, 1), |
838 | BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, -2), |
839 | BPF_EXIT_INSN() |
840 | }; |
841 | |
842 | probe_misc_feature(insns, ARRAY_SIZE(insns), |
843 | define_prefix, ifindex, |
844 | feat_name: "have_bounded_loops" , |
845 | plain_name: "Bounded loop support" , |
846 | define_name: "BOUNDED_LOOPS" ); |
847 | } |
848 | |
849 | /* |
850 | * Probe for the v2 instruction set extension introduced in commit 92b31a9af73b |
851 | * ("bpf: add BPF_J{LT,LE,SLT,SLE} instructions"). |
852 | */ |
853 | static void |
854 | probe_v2_isa_extension(const char *define_prefix, __u32 ifindex) |
855 | { |
856 | struct bpf_insn insns[4] = { |
857 | BPF_MOV64_IMM(BPF_REG_0, 0), |
858 | BPF_JMP_IMM(BPF_JLT, BPF_REG_0, 0, 1), |
859 | BPF_MOV64_IMM(BPF_REG_0, 1), |
860 | BPF_EXIT_INSN() |
861 | }; |
862 | |
863 | probe_misc_feature(insns, ARRAY_SIZE(insns), |
864 | define_prefix, ifindex, |
865 | feat_name: "have_v2_isa_extension" , |
866 | plain_name: "ISA extension v2" , |
867 | define_name: "V2_ISA_EXTENSION" ); |
868 | } |
869 | |
870 | /* |
871 | * Probe for the v3 instruction set extension introduced in commit 092ed0968bb6 |
872 | * ("bpf: verifier support JMP32"). |
873 | */ |
874 | static void |
875 | probe_v3_isa_extension(const char *define_prefix, __u32 ifindex) |
876 | { |
877 | struct bpf_insn insns[4] = { |
878 | BPF_MOV64_IMM(BPF_REG_0, 0), |
879 | BPF_JMP32_IMM(BPF_JLT, BPF_REG_0, 0, 1), |
880 | BPF_MOV64_IMM(BPF_REG_0, 1), |
881 | BPF_EXIT_INSN() |
882 | }; |
883 | |
884 | probe_misc_feature(insns, ARRAY_SIZE(insns), |
885 | define_prefix, ifindex, |
886 | feat_name: "have_v3_isa_extension" , |
887 | plain_name: "ISA extension v3" , |
888 | define_name: "V3_ISA_EXTENSION" ); |
889 | } |
890 | |
891 | static void |
892 | section_system_config(enum probe_component target, const char *define_prefix) |
893 | { |
894 | switch (target) { |
895 | case COMPONENT_KERNEL: |
896 | case COMPONENT_UNSPEC: |
897 | print_start_section(json_title: "system_config" , |
898 | plain_title: "Scanning system configuration..." , |
899 | define_comment: "/*** Misc kernel config items ***/" , |
900 | define_prefix); |
901 | if (!define_prefix) { |
902 | if (check_procfs()) { |
903 | probe_unprivileged_disabled(); |
904 | probe_jit_enable(); |
905 | probe_jit_harden(); |
906 | probe_jit_kallsyms(); |
907 | probe_jit_limit(); |
908 | } else { |
909 | p_info(fmt: "/* procfs not mounted, skipping related probes */" ); |
910 | } |
911 | } |
912 | probe_kernel_image_config(define_prefix); |
913 | print_end_section(); |
914 | break; |
915 | default: |
916 | break; |
917 | } |
918 | } |
919 | |
920 | static bool section_syscall_config(const char *define_prefix) |
921 | { |
922 | bool res; |
923 | |
924 | print_start_section(json_title: "syscall_config" , |
925 | plain_title: "Scanning system call availability..." , |
926 | define_comment: "/*** System call availability ***/" , |
927 | define_prefix); |
928 | res = probe_bpf_syscall(define_prefix); |
929 | print_end_section(); |
930 | |
931 | return res; |
932 | } |
933 | |
934 | static void |
935 | section_program_types(bool *supported_types, const char *define_prefix, |
936 | __u32 ifindex) |
937 | { |
938 | unsigned int prog_type = BPF_PROG_TYPE_UNSPEC; |
939 | const char *prog_type_str; |
940 | |
941 | print_start_section(json_title: "program_types" , |
942 | plain_title: "Scanning eBPF program types..." , |
943 | define_comment: "/*** eBPF program types ***/" , |
944 | define_prefix); |
945 | |
946 | while (true) { |
947 | prog_type++; |
948 | prog_type_str = libbpf_bpf_prog_type_str(prog_type); |
949 | /* libbpf will return NULL for variants unknown to it. */ |
950 | if (!prog_type_str) |
951 | break; |
952 | |
953 | probe_prog_type(prog_type, prog_type_str, supported_types, define_prefix, |
954 | ifindex); |
955 | } |
956 | |
957 | print_end_section(); |
958 | } |
959 | |
960 | static void section_map_types(const char *define_prefix, __u32 ifindex) |
961 | { |
962 | unsigned int map_type = BPF_MAP_TYPE_UNSPEC; |
963 | const char *map_type_str; |
964 | |
965 | print_start_section(json_title: "map_types" , |
966 | plain_title: "Scanning eBPF map types..." , |
967 | define_comment: "/*** eBPF map types ***/" , |
968 | define_prefix); |
969 | |
970 | while (true) { |
971 | map_type++; |
972 | map_type_str = libbpf_bpf_map_type_str(map_type); |
973 | /* libbpf will return NULL for variants unknown to it. */ |
974 | if (!map_type_str) |
975 | break; |
976 | |
977 | probe_map_type(map_type, map_type_str, define_prefix, ifindex); |
978 | } |
979 | |
980 | print_end_section(); |
981 | } |
982 | |
983 | static void |
984 | section_helpers(bool *supported_types, const char *define_prefix, __u32 ifindex) |
985 | { |
986 | unsigned int prog_type = BPF_PROG_TYPE_UNSPEC; |
987 | const char *prog_type_str; |
988 | |
989 | print_start_section(json_title: "helpers" , |
990 | plain_title: "Scanning eBPF helper functions..." , |
991 | define_comment: "/*** eBPF helper functions ***/" , |
992 | define_prefix); |
993 | |
994 | if (define_prefix) |
995 | printf("/*\n" |
996 | " * Use %sHAVE_PROG_TYPE_HELPER(prog_type_name, helper_name)\n" |
997 | " * to determine if <helper_name> is available for <prog_type_name>,\n" |
998 | " * e.g.\n" |
999 | " * #if %sHAVE_PROG_TYPE_HELPER(xdp, bpf_redirect)\n" |
1000 | " * // do stuff with this helper\n" |
1001 | " * #elif\n" |
1002 | " * // use a workaround\n" |
1003 | " * #endif\n" |
1004 | " */\n" |
1005 | "#define %sHAVE_PROG_TYPE_HELPER(prog_type, helper) \\\n" |
1006 | " %sBPF__PROG_TYPE_ ## prog_type ## __HELPER_ ## helper\n" , |
1007 | define_prefix, define_prefix, define_prefix, |
1008 | define_prefix); |
1009 | while (true) { |
1010 | prog_type++; |
1011 | prog_type_str = libbpf_bpf_prog_type_str(prog_type); |
1012 | /* libbpf will return NULL for variants unknown to it. */ |
1013 | if (!prog_type_str) |
1014 | break; |
1015 | |
1016 | probe_helpers_for_progtype(prog_type, prog_type_str, |
1017 | supported_type: supported_types[prog_type], |
1018 | define_prefix, |
1019 | ifindex); |
1020 | } |
1021 | |
1022 | print_end_section(); |
1023 | } |
1024 | |
1025 | static void section_misc(const char *define_prefix, __u32 ifindex) |
1026 | { |
1027 | print_start_section(json_title: "misc" , |
1028 | plain_title: "Scanning miscellaneous eBPF features..." , |
1029 | define_comment: "/*** eBPF misc features ***/" , |
1030 | define_prefix); |
1031 | probe_large_insn_limit(define_prefix, ifindex); |
1032 | probe_bounded_loops(define_prefix, ifindex); |
1033 | probe_v2_isa_extension(define_prefix, ifindex); |
1034 | probe_v3_isa_extension(define_prefix, ifindex); |
1035 | print_end_section(); |
1036 | } |
1037 | |
1038 | #ifdef USE_LIBCAP |
1039 | #define capability(c) { c, false, #c } |
1040 | #define capability_msg(a, i) a[i].set ? "" : a[i].name, a[i].set ? "" : ", " |
1041 | #endif |
1042 | |
1043 | static int handle_perms(void) |
1044 | { |
1045 | #ifdef USE_LIBCAP |
1046 | struct { |
1047 | cap_value_t cap; |
1048 | bool set; |
1049 | char name[14]; /* strlen("CAP_SYS_ADMIN") */ |
1050 | } bpf_caps[] = { |
1051 | capability(CAP_SYS_ADMIN), |
1052 | #ifdef CAP_BPF |
1053 | capability(CAP_BPF), |
1054 | capability(CAP_NET_ADMIN), |
1055 | capability(CAP_PERFMON), |
1056 | #endif |
1057 | }; |
1058 | cap_value_t cap_list[ARRAY_SIZE(bpf_caps)]; |
1059 | unsigned int i, nb_bpf_caps = 0; |
1060 | bool cap_sys_admin_only = true; |
1061 | cap_flag_value_t val; |
1062 | int res = -1; |
1063 | cap_t caps; |
1064 | |
1065 | caps = cap_get_proc(); |
1066 | if (!caps) { |
1067 | p_err("failed to get capabilities for process: %s" , |
1068 | strerror(errno)); |
1069 | return -1; |
1070 | } |
1071 | |
1072 | #ifdef CAP_BPF |
1073 | if (CAP_IS_SUPPORTED(CAP_BPF)) |
1074 | cap_sys_admin_only = false; |
1075 | #endif |
1076 | |
1077 | for (i = 0; i < ARRAY_SIZE(bpf_caps); i++) { |
1078 | const char *cap_name = bpf_caps[i].name; |
1079 | cap_value_t cap = bpf_caps[i].cap; |
1080 | |
1081 | if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &val)) { |
1082 | p_err("bug: failed to retrieve %s status: %s" , cap_name, |
1083 | strerror(errno)); |
1084 | goto exit_free; |
1085 | } |
1086 | |
1087 | if (val == CAP_SET) { |
1088 | bpf_caps[i].set = true; |
1089 | cap_list[nb_bpf_caps++] = cap; |
1090 | } |
1091 | |
1092 | if (cap_sys_admin_only) |
1093 | /* System does not know about CAP_BPF, meaning that |
1094 | * CAP_SYS_ADMIN is the only capability required. We |
1095 | * just checked it, break. |
1096 | */ |
1097 | break; |
1098 | } |
1099 | |
1100 | if ((run_as_unprivileged && !nb_bpf_caps) || |
1101 | (!run_as_unprivileged && nb_bpf_caps == ARRAY_SIZE(bpf_caps)) || |
1102 | (!run_as_unprivileged && cap_sys_admin_only && nb_bpf_caps)) { |
1103 | /* We are all good, exit now */ |
1104 | res = 0; |
1105 | goto exit_free; |
1106 | } |
1107 | |
1108 | if (!run_as_unprivileged) { |
1109 | if (cap_sys_admin_only) |
1110 | p_err("missing %s, required for full feature probing; run as root or use 'unprivileged'" , |
1111 | bpf_caps[0].name); |
1112 | else |
1113 | p_err("missing %s%s%s%s%s%s%s%srequired for full feature probing; run as root or use 'unprivileged'" , |
1114 | capability_msg(bpf_caps, 0), |
1115 | #ifdef CAP_BPF |
1116 | capability_msg(bpf_caps, 1), |
1117 | capability_msg(bpf_caps, 2), |
1118 | capability_msg(bpf_caps, 3) |
1119 | #else |
1120 | "" , "" , "" , "" , "" , "" |
1121 | #endif /* CAP_BPF */ |
1122 | ); |
1123 | goto exit_free; |
1124 | } |
1125 | |
1126 | /* if (run_as_unprivileged && nb_bpf_caps > 0), drop capabilities. */ |
1127 | if (cap_set_flag(caps, CAP_EFFECTIVE, nb_bpf_caps, cap_list, |
1128 | CAP_CLEAR)) { |
1129 | p_err("bug: failed to clear capabilities: %s" , strerror(errno)); |
1130 | goto exit_free; |
1131 | } |
1132 | |
1133 | if (cap_set_proc(caps)) { |
1134 | p_err("failed to drop capabilities: %s" , strerror(errno)); |
1135 | goto exit_free; |
1136 | } |
1137 | |
1138 | res = 0; |
1139 | |
1140 | exit_free: |
1141 | if (cap_free(caps) && !res) { |
1142 | p_err("failed to clear storage object for capabilities: %s" , |
1143 | strerror(errno)); |
1144 | res = -1; |
1145 | } |
1146 | |
1147 | return res; |
1148 | #else |
1149 | /* Detection assumes user has specific privileges. |
1150 | * We do not use libcap so let's approximate, and restrict usage to |
1151 | * root user only. |
1152 | */ |
1153 | if (geteuid()) { |
1154 | p_err(fmt: "full feature probing requires root privileges" ); |
1155 | return -1; |
1156 | } |
1157 | |
1158 | return 0; |
1159 | #endif /* USE_LIBCAP */ |
1160 | } |
1161 | |
1162 | static int do_probe(int argc, char **argv) |
1163 | { |
1164 | enum probe_component target = COMPONENT_UNSPEC; |
1165 | const char *define_prefix = NULL; |
1166 | bool supported_types[128] = {}; |
1167 | __u32 ifindex = 0; |
1168 | char *ifname; |
1169 | |
1170 | set_max_rlimit(); |
1171 | |
1172 | while (argc) { |
1173 | if (is_prefix(pfx: *argv, str: "kernel" )) { |
1174 | if (target != COMPONENT_UNSPEC) { |
1175 | p_err(fmt: "component to probe already specified" ); |
1176 | return -1; |
1177 | } |
1178 | target = COMPONENT_KERNEL; |
1179 | NEXT_ARG(); |
1180 | } else if (is_prefix(pfx: *argv, str: "dev" )) { |
1181 | NEXT_ARG(); |
1182 | |
1183 | if (target != COMPONENT_UNSPEC || ifindex) { |
1184 | p_err(fmt: "component to probe already specified" ); |
1185 | return -1; |
1186 | } |
1187 | if (!REQ_ARGS(1)) |
1188 | return -1; |
1189 | |
1190 | target = COMPONENT_DEVICE; |
1191 | ifname = GET_ARG(); |
1192 | ifindex = if_nametoindex(ifname); |
1193 | if (!ifindex) { |
1194 | p_err("unrecognized netdevice '%s': %s" , ifname, |
1195 | strerror(errno)); |
1196 | return -1; |
1197 | } |
1198 | } else if (is_prefix(pfx: *argv, str: "full" )) { |
1199 | full_mode = true; |
1200 | NEXT_ARG(); |
1201 | } else if (is_prefix(pfx: *argv, str: "macros" ) && !define_prefix) { |
1202 | define_prefix = "" ; |
1203 | NEXT_ARG(); |
1204 | } else if (is_prefix(pfx: *argv, str: "prefix" )) { |
1205 | if (!define_prefix) { |
1206 | p_err(fmt: "'prefix' argument can only be use after 'macros'" ); |
1207 | return -1; |
1208 | } |
1209 | if (strcmp(define_prefix, "" )) { |
1210 | p_err(fmt: "'prefix' already defined" ); |
1211 | return -1; |
1212 | } |
1213 | NEXT_ARG(); |
1214 | |
1215 | if (!REQ_ARGS(1)) |
1216 | return -1; |
1217 | define_prefix = GET_ARG(); |
1218 | } else if (is_prefix(pfx: *argv, str: "unprivileged" )) { |
1219 | #ifdef USE_LIBCAP |
1220 | run_as_unprivileged = true; |
1221 | NEXT_ARG(); |
1222 | #else |
1223 | p_err(fmt: "unprivileged run not supported, recompile bpftool with libcap" ); |
1224 | return -1; |
1225 | #endif |
1226 | } else { |
1227 | p_err(fmt: "expected no more arguments, 'kernel', 'dev', 'macros' or 'prefix', got: '%s'?" , |
1228 | *argv); |
1229 | return -1; |
1230 | } |
1231 | } |
1232 | |
1233 | /* Full feature detection requires specific privileges. |
1234 | * Let's approximate, and warn if user is not root. |
1235 | */ |
1236 | if (handle_perms()) |
1237 | return -1; |
1238 | |
1239 | if (json_output) { |
1240 | define_prefix = NULL; |
1241 | jsonw_start_object(self: json_wtr); |
1242 | } |
1243 | |
1244 | section_system_config(target, define_prefix); |
1245 | if (!section_syscall_config(define_prefix)) |
1246 | /* bpf() syscall unavailable, don't probe other BPF features */ |
1247 | goto exit_close_json; |
1248 | section_program_types(supported_types, define_prefix, ifindex); |
1249 | section_map_types(define_prefix, ifindex); |
1250 | section_helpers(supported_types, define_prefix, ifindex); |
1251 | section_misc(define_prefix, ifindex); |
1252 | |
1253 | exit_close_json: |
1254 | if (json_output) |
1255 | /* End root object */ |
1256 | jsonw_end_object(self: json_wtr); |
1257 | |
1258 | return 0; |
1259 | } |
1260 | |
1261 | static const char *get_helper_name(unsigned int id) |
1262 | { |
1263 | if (id >= ARRAY_SIZE(helper_name)) |
1264 | return NULL; |
1265 | |
1266 | return helper_name[id]; |
1267 | } |
1268 | |
1269 | static int do_list_builtins(int argc, char **argv) |
1270 | { |
1271 | const char *(*get_name)(unsigned int id); |
1272 | unsigned int id = 0; |
1273 | |
1274 | if (argc < 1) |
1275 | usage(); |
1276 | |
1277 | if (is_prefix(pfx: *argv, str: "prog_types" )) { |
1278 | get_name = (const char *(*)(unsigned int))libbpf_bpf_prog_type_str; |
1279 | } else if (is_prefix(pfx: *argv, str: "map_types" )) { |
1280 | get_name = (const char *(*)(unsigned int))libbpf_bpf_map_type_str; |
1281 | } else if (is_prefix(pfx: *argv, str: "attach_types" )) { |
1282 | get_name = (const char *(*)(unsigned int))libbpf_bpf_attach_type_str; |
1283 | } else if (is_prefix(pfx: *argv, str: "link_types" )) { |
1284 | get_name = (const char *(*)(unsigned int))libbpf_bpf_link_type_str; |
1285 | } else if (is_prefix(pfx: *argv, str: "helpers" )) { |
1286 | get_name = get_helper_name; |
1287 | } else { |
1288 | p_err(fmt: "expected 'prog_types', 'map_types', 'attach_types', 'link_types' or 'helpers', got: %s" , *argv); |
1289 | return -1; |
1290 | } |
1291 | |
1292 | if (json_output) |
1293 | jsonw_start_array(self: json_wtr); /* root array */ |
1294 | |
1295 | while (true) { |
1296 | const char *name; |
1297 | |
1298 | name = get_name(id++); |
1299 | if (!name) |
1300 | break; |
1301 | if (json_output) |
1302 | jsonw_string(self: json_wtr, value: name); |
1303 | else |
1304 | printf("%s\n" , name); |
1305 | } |
1306 | |
1307 | if (json_output) |
1308 | jsonw_end_array(self: json_wtr); /* root array */ |
1309 | |
1310 | return 0; |
1311 | } |
1312 | |
1313 | static int do_help(int argc, char **argv) |
1314 | { |
1315 | if (json_output) { |
1316 | jsonw_null(self: json_wtr); |
1317 | return 0; |
1318 | } |
1319 | |
1320 | fprintf(stderr, |
1321 | "Usage: %1$s %2$s probe [COMPONENT] [full] [unprivileged] [macros [prefix PREFIX]]\n" |
1322 | " %1$s %2$s list_builtins GROUP\n" |
1323 | " %1$s %2$s help\n" |
1324 | "\n" |
1325 | " COMPONENT := { kernel | dev NAME }\n" |
1326 | " GROUP := { prog_types | map_types | attach_types | link_types | helpers }\n" |
1327 | " " HELP_SPEC_OPTIONS " }\n" |
1328 | "" , |
1329 | bin_name, argv[-2]); |
1330 | |
1331 | return 0; |
1332 | } |
1333 | |
1334 | static const struct cmd cmds[] = { |
1335 | { "probe" , do_probe }, |
1336 | { "list_builtins" , do_list_builtins }, |
1337 | { "help" , do_help }, |
1338 | { 0 } |
1339 | }; |
1340 | |
1341 | int do_feature(int argc, char **argv) |
1342 | { |
1343 | return cmd_select(cmds, argc, argv, help: do_help); |
1344 | } |
1345 | |