Commit | Line | Data |
---|---|---|
49eb7ab3 QM |
1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (c) 2019 Netronome Systems, Inc. */ | |
3 | ||
1bf4b058 | 4 | #include <ctype.h> |
49eb7ab3 | 5 | #include <errno.h> |
f655c088 | 6 | #include <fcntl.h> |
49eb7ab3 QM |
7 | #include <string.h> |
8 | #include <unistd.h> | |
f9499fed | 9 | #include <net/if.h> |
0b3b9ca3 | 10 | #ifdef USE_LIBCAP |
cf9bf714 | 11 | #include <sys/capability.h> |
0b3b9ca3 | 12 | #endif |
49eb7ab3 | 13 | #include <sys/utsname.h> |
7a4522bb | 14 | #include <sys/vfs.h> |
49eb7ab3 QM |
15 | |
16 | #include <linux/filter.h> | |
17 | #include <linux/limits.h> | |
18 | ||
229c3b47 THJ |
19 | #include <bpf/bpf.h> |
20 | #include <bpf/libbpf.h> | |
a664a834 | 21 | #include <zlib.h> |
49eb7ab3 QM |
22 | |
23 | #include "main.h" | |
24 | ||
7a4522bb QM |
25 | #ifndef PROC_SUPER_MAGIC |
26 | # define PROC_SUPER_MAGIC 0x9fa0 | |
27 | #endif | |
28 | ||
49eb7ab3 QM |
29 | enum probe_component { |
30 | COMPONENT_UNSPEC, | |
31 | COMPONENT_KERNEL, | |
f9499fed | 32 | COMPONENT_DEVICE, |
49eb7ab3 QM |
33 | }; |
34 | ||
2d3ea5e8 QM |
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 | ||
e3450b79 | 42 | static bool full_mode; |
0b3b9ca3 | 43 | #ifdef USE_LIBCAP |
cf9bf714 | 44 | static bool run_as_unprivileged; |
0b3b9ca3 | 45 | #endif |
e3450b79 | 46 | |
7a4522bb QM |
47 | /* Miscellaneous utility functions */ |
48 | ||
f655c088 NS |
49 | static bool grep(const char *buffer, const char *pattern) |
50 | { | |
51 | return !!strstr(buffer, pattern); | |
52 | } | |
53 | ||
7a4522bb QM |
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 | ||
d267cff4 QM |
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 | ||
49eb7ab3 QM |
74 | /* Printing utility functions */ |
75 | ||
76 | static void | |
d267cff4 QM |
77 | print_bool_feature(const char *feat_name, const char *plain_name, |
78 | const char *define_name, bool res, const char *define_prefix) | |
49eb7ab3 QM |
79 | { |
80 | if (json_output) | |
81 | jsonw_bool_field(json_wtr, feat_name, res); | |
d267cff4 QM |
82 | else if (define_prefix) |
83 | printf("#define %s%sHAVE_%s\n", define_prefix, | |
84 | res ? "" : "NO_", define_name); | |
49eb7ab3 QM |
85 | else |
86 | printf("%s is %savailable\n", plain_name, res ? "" : "NOT "); | |
87 | } | |
88 | ||
0ee52c0f DB |
89 | static void print_kernel_option(const char *name, const char *value, |
90 | const char *define_prefix) | |
4567b983 QM |
91 | { |
92 | char *endptr; | |
93 | int res; | |
94 | ||
95 | if (json_output) { | |
96 | if (!value) { | |
97 | jsonw_null_field(json_wtr, name); | |
98 | return; | |
99 | } | |
100 | errno = 0; | |
101 | res = strtol(value, &endptr, 0); | |
102 | if (!errno && *endptr == '\n') | |
103 | jsonw_int_field(json_wtr, name, res); | |
104 | else | |
105 | jsonw_string_field(json_wtr, name, value); | |
0ee52c0f DB |
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); | |
4567b983 QM |
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 | ||
49eb7ab3 | 120 | static void |
d267cff4 QM |
121 | print_start_section(const char *json_title, const char *plain_title, |
122 | const char *define_comment, const char *define_prefix) | |
49eb7ab3 QM |
123 | { |
124 | if (json_output) { | |
125 | jsonw_name(json_wtr, json_title); | |
126 | jsonw_start_object(json_wtr); | |
d267cff4 QM |
127 | } else if (define_prefix) { |
128 | printf("%s\n", define_comment); | |
49eb7ab3 QM |
129 | } else { |
130 | printf("%s\n", plain_title); | |
131 | } | |
132 | } | |
133 | ||
6b52ca44 | 134 | static void print_end_section(void) |
1bf4b058 QM |
135 | { |
136 | if (json_output) | |
137 | jsonw_end_object(json_wtr); | |
138 | else | |
139 | printf("\n"); | |
1bf4b058 QM |
140 | } |
141 | ||
49eb7ab3 QM |
142 | /* Probing functions */ |
143 | ||
f655c088 NS |
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 | ||
7a4522bb QM |
170 | static int read_procfs(const char *path) |
171 | { | |
172 | char *endptr, *line = NULL; | |
173 | size_t len = 0; | |
174 | FILE *fd; | |
175 | int 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 | int res; | |
198 | ||
d267cff4 QM |
199 | /* No support for C-style ouptut */ |
200 | ||
7a4522bb QM |
201 | res = read_procfs("/proc/sys/kernel/unprivileged_bpf_disabled"); |
202 | if (json_output) { | |
203 | jsonw_int_field(json_wtr, "unprivileged_bpf_disabled", res); | |
204 | } else { | |
205 | switch (res) { | |
206 | case 0: | |
207 | printf("bpf() syscall for unprivileged users is enabled\n"); | |
208 | break; | |
209 | case 1: | |
8c1b2119 ML |
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"); | |
7a4522bb QM |
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 %d\n", res); | |
220 | } | |
221 | } | |
222 | } | |
223 | ||
224 | static void probe_jit_enable(void) | |
225 | { | |
226 | int res; | |
227 | ||
d267cff4 QM |
228 | /* No support for C-style ouptut */ |
229 | ||
7a4522bb QM |
230 | res = read_procfs("/proc/sys/net/core/bpf_jit_enable"); |
231 | if (json_output) { | |
232 | jsonw_int_field(json_wtr, "bpf_jit_enable", 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 %d\n", | |
249 | res); | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
254 | static void probe_jit_harden(void) | |
255 | { | |
256 | int res; | |
257 | ||
d267cff4 QM |
258 | /* No support for C-style ouptut */ |
259 | ||
7a4522bb QM |
260 | res = read_procfs("/proc/sys/net/core/bpf_jit_harden"); |
261 | if (json_output) { | |
262 | jsonw_int_field(json_wtr, "bpf_jit_harden", 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 %d\n", | |
279 | res); | |
280 | } | |
281 | } | |
282 | } | |
283 | ||
284 | static void probe_jit_kallsyms(void) | |
285 | { | |
286 | int res; | |
287 | ||
d267cff4 QM |
288 | /* No support for C-style ouptut */ |
289 | ||
7a4522bb QM |
290 | res = read_procfs("/proc/sys/net/core/bpf_jit_kallsyms"); |
291 | if (json_output) { | |
292 | jsonw_int_field(json_wtr, "bpf_jit_kallsyms", 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 %d\n", res); | |
306 | } | |
307 | } | |
308 | } | |
309 | ||
310 | static void probe_jit_limit(void) | |
311 | { | |
312 | int res; | |
313 | ||
d267cff4 QM |
314 | /* No support for C-style ouptut */ |
315 | ||
7a4522bb QM |
316 | res = read_procfs("/proc/sys/net/core/bpf_jit_limit"); |
317 | if (json_output) { | |
318 | jsonw_int_field(json_wtr, "bpf_jit_limit", 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 %d bytes\n", res); | |
326 | } | |
327 | } | |
328 | } | |
329 | ||
a664a834 PW |
330 | static bool read_next_kernel_config_option(gzFile file, char *buf, size_t n, |
331 | char **value) | |
4567b983 | 332 | { |
a664a834 | 333 | char *sep; |
4567b983 | 334 | |
a664a834 PW |
335 | while (gzgets(file, buf, n)) { |
336 | if (strncmp(buf, "CONFIG_", 7)) | |
4567b983 | 337 | continue; |
a664a834 PW |
338 | |
339 | sep = strchr(buf, '='); | |
340 | if (!sep) | |
4567b983 QM |
341 | continue; |
342 | ||
343 | /* Trim ending '\n' */ | |
a664a834 PW |
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; | |
4567b983 | 350 | |
a664a834 PW |
351 | *value = sep + 1; |
352 | return true; | |
4567b983 | 353 | } |
4567b983 | 354 | |
a664a834 | 355 | return false; |
4567b983 QM |
356 | } |
357 | ||
0ee52c0f | 358 | static void probe_kernel_image_config(const char *define_prefix) |
4567b983 | 359 | { |
0ee52c0f DB |
360 | static const struct { |
361 | const char * const name; | |
362 | bool macro_dump; | |
363 | } options[] = { | |
4567b983 | 364 | /* Enable BPF */ |
0ee52c0f | 365 | { "CONFIG_BPF", }, |
4567b983 | 366 | /* Enable bpf() syscall */ |
0ee52c0f | 367 | { "CONFIG_BPF_SYSCALL", }, |
4567b983 | 368 | /* Does selected architecture support eBPF JIT compiler */ |
0ee52c0f | 369 | { "CONFIG_HAVE_EBPF_JIT", }, |
4567b983 | 370 | /* Compile eBPF JIT compiler */ |
0ee52c0f | 371 | { "CONFIG_BPF_JIT", }, |
4567b983 | 372 | /* Avoid compiling eBPF interpreter (use JIT only) */ |
0ee52c0f | 373 | { "CONFIG_BPF_JIT_ALWAYS_ON", }, |
b9fc8b4a GS |
374 | /* Kernel BTF debug information available */ |
375 | { "CONFIG_DEBUG_INFO_BTF", }, | |
376 | /* Kernel module BTF debug information available */ | |
377 | { "CONFIG_DEBUG_INFO_BTF_MODULES", }, | |
4567b983 QM |
378 | |
379 | /* cgroups */ | |
0ee52c0f | 380 | { "CONFIG_CGROUPS", }, |
4567b983 | 381 | /* BPF programs attached to cgroups */ |
0ee52c0f | 382 | { "CONFIG_CGROUP_BPF", }, |
4567b983 | 383 | /* bpf_get_cgroup_classid() helper */ |
0ee52c0f | 384 | { "CONFIG_CGROUP_NET_CLASSID", }, |
4567b983 | 385 | /* bpf_skb_{,ancestor_}cgroup_id() helpers */ |
0ee52c0f | 386 | { "CONFIG_SOCK_CGROUP_DATA", }, |
4567b983 QM |
387 | |
388 | /* Tracing: attach BPF to kprobes, tracepoints, etc. */ | |
0ee52c0f | 389 | { "CONFIG_BPF_EVENTS", }, |
4567b983 | 390 | /* Kprobes */ |
0ee52c0f | 391 | { "CONFIG_KPROBE_EVENTS", }, |
4567b983 | 392 | /* Uprobes */ |
0ee52c0f | 393 | { "CONFIG_UPROBE_EVENTS", }, |
4567b983 | 394 | /* Tracepoints */ |
0ee52c0f | 395 | { "CONFIG_TRACING", }, |
4567b983 | 396 | /* Syscall tracepoints */ |
0ee52c0f | 397 | { "CONFIG_FTRACE_SYSCALLS", }, |
4567b983 | 398 | /* bpf_override_return() helper support for selected arch */ |
0ee52c0f | 399 | { "CONFIG_FUNCTION_ERROR_INJECTION", }, |
4567b983 | 400 | /* bpf_override_return() helper */ |
0ee52c0f | 401 | { "CONFIG_BPF_KPROBE_OVERRIDE", }, |
4567b983 QM |
402 | |
403 | /* Network */ | |
0ee52c0f | 404 | { "CONFIG_NET", }, |
4567b983 | 405 | /* AF_XDP sockets */ |
0ee52c0f | 406 | { "CONFIG_XDP_SOCKETS", }, |
4567b983 | 407 | /* BPF_PROG_TYPE_LWT_* and related helpers */ |
0ee52c0f | 408 | { "CONFIG_LWTUNNEL_BPF", }, |
4567b983 | 409 | /* BPF_PROG_TYPE_SCHED_ACT, TC (traffic control) actions */ |
0ee52c0f | 410 | { "CONFIG_NET_ACT_BPF", }, |
4567b983 | 411 | /* BPF_PROG_TYPE_SCHED_CLS, TC filters */ |
0ee52c0f | 412 | { "CONFIG_NET_CLS_BPF", }, |
4567b983 | 413 | /* TC clsact qdisc */ |
0ee52c0f | 414 | { "CONFIG_NET_CLS_ACT", }, |
4567b983 | 415 | /* Ingress filtering with TC */ |
0ee52c0f | 416 | { "CONFIG_NET_SCH_INGRESS", }, |
4567b983 | 417 | /* bpf_skb_get_xfrm_state() helper */ |
0ee52c0f | 418 | { "CONFIG_XFRM", }, |
4567b983 | 419 | /* bpf_get_route_realm() helper */ |
0ee52c0f | 420 | { "CONFIG_IP_ROUTE_CLASSID", }, |
4567b983 | 421 | /* BPF_PROG_TYPE_LWT_SEG6_LOCAL and related helpers */ |
0ee52c0f | 422 | { "CONFIG_IPV6_SEG6_BPF", }, |
4567b983 | 423 | /* BPF_PROG_TYPE_LIRC_MODE2 and related helpers */ |
0ee52c0f | 424 | { "CONFIG_BPF_LIRC_MODE2", }, |
4567b983 | 425 | /* BPF stream parser and BPF socket maps */ |
0ee52c0f | 426 | { "CONFIG_BPF_STREAM_PARSER", }, |
4567b983 | 427 | /* xt_bpf module for passing BPF programs to netfilter */ |
0ee52c0f | 428 | { "CONFIG_NETFILTER_XT_MATCH_BPF", }, |
4567b983 | 429 | /* bpfilter back-end for iptables */ |
0ee52c0f | 430 | { "CONFIG_BPFILTER", }, |
4567b983 | 431 | /* bpftilter module with "user mode helper" */ |
0ee52c0f | 432 | { "CONFIG_BPFILTER_UMH", }, |
4567b983 QM |
433 | |
434 | /* test_bpf module for BPF tests */ | |
0ee52c0f DB |
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, } | |
4567b983 | 440 | }; |
a664a834 | 441 | char *values[ARRAY_SIZE(options)] = { }; |
4567b983 QM |
442 | struct utsname utsn; |
443 | char path[PATH_MAX]; | |
a664a834 PW |
444 | gzFile file = NULL; |
445 | char buf[4096]; | |
446 | char *value; | |
447 | size_t i; | |
4567b983 | 448 | |
a664a834 PW |
449 | if (!uname(&utsn)) { |
450 | snprintf(path, sizeof(path), "/boot/config-%s", utsn.release); | |
4567b983 | 451 | |
a664a834 PW |
452 | /* gzopen also accepts uncompressed files. */ |
453 | file = gzopen(path, "r"); | |
454 | } | |
4567b983 | 455 | |
a664a834 PW |
456 | if (!file) { |
457 | /* Some distributions build with CONFIG_IKCONFIG=y and put the | |
458 | * config file at /proc/config.gz. | |
4567b983 | 459 | */ |
a664a834 | 460 | file = gzopen("/proc/config.gz", "r"); |
4567b983 | 461 | } |
a664a834 | 462 | if (!file) { |
4567b983 QM |
463 | p_info("skipping kernel config, can't open file: %s", |
464 | strerror(errno)); | |
a664a834 | 465 | goto end_parse; |
4567b983 QM |
466 | } |
467 | /* Sanity checks */ | |
a664a834 PW |
468 | if (!gzgets(file, buf, sizeof(buf)) || |
469 | !gzgets(file, buf, sizeof(buf))) { | |
4567b983 QM |
470 | p_info("skipping kernel config, can't read from file: %s", |
471 | strerror(errno)); | |
a664a834 | 472 | goto end_parse; |
4567b983 QM |
473 | } |
474 | if (strcmp(buf, "# Automatically generated file; DO NOT EDIT.\n")) { | |
475 | p_info("skipping kernel config, can't find correct file"); | |
a664a834 | 476 | goto end_parse; |
4567b983 | 477 | } |
4567b983 | 478 | |
a664a834 PW |
479 | while (read_next_kernel_config_option(file, buf, sizeof(buf), &value)) { |
480 | for (i = 0; i < ARRAY_SIZE(options); i++) { | |
0ee52c0f DB |
481 | if ((define_prefix && !options[i].macro_dump) || |
482 | values[i] || strcmp(buf, options[i].name)) | |
a664a834 PW |
483 | continue; |
484 | ||
485 | values[i] = strdup(value); | |
486 | } | |
4567b983 | 487 | } |
4567b983 | 488 | |
a664a834 | 489 | for (i = 0; i < ARRAY_SIZE(options); i++) { |
0ee52c0f DB |
490 | if (define_prefix && !options[i].macro_dump) |
491 | continue; | |
492 | print_kernel_option(options[i].name, values[i], define_prefix); | |
a664a834 PW |
493 | free(values[i]); |
494 | } | |
75514e4c CS |
495 | |
496 | end_parse: | |
497 | if (file) | |
498 | gzclose(file); | |
4567b983 QM |
499 | } |
500 | ||
d267cff4 | 501 | static bool probe_bpf_syscall(const char *define_prefix) |
49eb7ab3 QM |
502 | { |
503 | bool res; | |
504 | ||
a3c7c7e8 | 505 | bpf_prog_load(BPF_PROG_TYPE_UNSPEC, NULL, NULL, NULL, 0, NULL); |
49eb7ab3 QM |
506 | res = (errno != ENOSYS); |
507 | ||
508 | print_bool_feature("have_bpf_syscall", | |
509 | "bpf() syscall", | |
d267cff4 QM |
510 | "BPF_SYSCALL", |
511 | res, define_prefix); | |
49eb7ab3 QM |
512 | |
513 | return res; | |
514 | } | |
515 | ||
f655c088 NS |
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, 0, ifindex); | |
548 | } | |
549 | ||
d267cff4 | 550 | static void |
b700eeb4 DM |
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) | |
1bf4b058 | 553 | { |
d267cff4 | 554 | char feat_name[128], plain_desc[128], define_name[128]; |
1bf4b058 | 555 | const char *plain_comment = "eBPF program_type "; |
1bf4b058 QM |
556 | size_t maxlen; |
557 | bool res; | |
558 | ||
1a56c18e | 559 | if (ifindex) { |
f655c088 NS |
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 { | |
7b53eaa6 | 570 | res = libbpf_probe_bpf_prog_type(prog_type, NULL) > 0; |
1a56c18e | 571 | } |
f9499fed | 572 | |
0b3b9ca3 | 573 | #ifdef USE_LIBCAP |
cf9bf714 QM |
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; | |
0b3b9ca3 | 579 | #endif |
1bf4b058 QM |
580 | |
581 | supported_types[prog_type] |= res; | |
582 | ||
583 | maxlen = sizeof(plain_desc) - strlen(plain_comment) - 1; | |
b700eeb4 | 584 | if (strlen(prog_type_str) > maxlen) { |
1bf4b058 QM |
585 | p_info("program type name too long"); |
586 | return; | |
587 | } | |
588 | ||
b700eeb4 DM |
589 | sprintf(feat_name, "have_%s_prog_type", prog_type_str); |
590 | sprintf(define_name, "%s_prog_type", prog_type_str); | |
d267cff4 | 591 | uppercase(define_name, sizeof(define_name)); |
b700eeb4 | 592 | sprintf(plain_desc, "%s%s", plain_comment, prog_type_str); |
d267cff4 QM |
593 | print_bool_feature(feat_name, plain_desc, define_name, res, |
594 | define_prefix); | |
1bf4b058 QM |
595 | } |
596 | ||
f655c088 NS |
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 | ||
d267cff4 | 617 | static void |
2e98964b DM |
618 | probe_map_type(enum bpf_map_type map_type, char const *map_type_str, |
619 | const char *define_prefix, __u32 ifindex) | |
f99e1663 | 620 | { |
d267cff4 | 621 | char feat_name[128], plain_desc[128], define_name[128]; |
f99e1663 | 622 | const char *plain_comment = "eBPF map_type "; |
f99e1663 QM |
623 | size_t maxlen; |
624 | bool res; | |
625 | ||
1a56c18e | 626 | if (ifindex) { |
f655c088 NS |
627 | switch (map_type) { |
628 | case BPF_MAP_TYPE_HASH: | |
629 | case BPF_MAP_TYPE_ARRAY: | |
630 | break; | |
631 | default: | |
632 | return; | |
633 | } | |
1a56c18e | 634 | |
f655c088 NS |
635 | res = probe_map_type_ifindex(map_type, ifindex); |
636 | } else { | |
6d9f63b9 | 637 | res = libbpf_probe_bpf_map_type(map_type, NULL) > 0; |
f655c088 | 638 | } |
f99e1663 | 639 | |
cf9bf714 QM |
640 | /* Probe result depends on the success of map creation, no additional |
641 | * check required for unprivileged users | |
642 | */ | |
643 | ||
f99e1663 | 644 | maxlen = sizeof(plain_desc) - strlen(plain_comment) - 1; |
2e98964b | 645 | if (strlen(map_type_str) > maxlen) { |
f99e1663 QM |
646 | p_info("map type name too long"); |
647 | return; | |
648 | } | |
649 | ||
2e98964b DM |
650 | sprintf(feat_name, "have_%s_map_type", map_type_str); |
651 | sprintf(define_name, "%s_map_type", map_type_str); | |
d267cff4 | 652 | uppercase(define_name, sizeof(define_name)); |
2e98964b | 653 | sprintf(plain_desc, "%s%s", plain_comment, map_type_str); |
d267cff4 QM |
654 | print_bool_feature(feat_name, plain_desc, define_name, res, |
655 | define_prefix); | |
f99e1663 QM |
656 | } |
657 | ||
f655c088 NS |
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), buf, | |
670 | sizeof(buf), ifindex); | |
671 | res = !grep(buf, "invalid func ") && !grep(buf, "unknown func "); | |
672 | ||
673 | switch (get_vendor_id(ifindex)) { | |
674 | case 0x19ee: /* Netronome specific */ | |
675 | res = res && !grep(buf, "not supported by FW") && | |
676 | !grep(buf, "unsupported function id"); | |
677 | break; | |
678 | default: | |
679 | break; | |
680 | } | |
681 | ||
682 | return res; | |
683 | } | |
684 | ||
b06a92a1 | 685 | static bool |
368cb0e7 MR |
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 | { | |
cf9bf714 | 690 | bool res = false; |
368cb0e7 | 691 | |
cf9bf714 | 692 | if (supported_type) { |
f655c088 NS |
693 | if (ifindex) |
694 | res = probe_helper_ifindex(id, prog_type, ifindex); | |
695 | else | |
6d9f63b9 | 696 | res = libbpf_probe_bpf_helper(prog_type, id, NULL) > 0; |
0b3b9ca3 | 697 | #ifdef USE_LIBCAP |
cf9bf714 QM |
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; | |
0b3b9ca3 | 704 | #endif |
cf9bf714 | 705 | } |
368cb0e7 MR |
706 | |
707 | if (json_output) { | |
708 | if (res) | |
709 | jsonw_string(json_wtr, 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 | } | |
b06a92a1 ML |
718 | |
719 | return res; | |
368cb0e7 MR |
720 | } |
721 | ||
2d3ea5e8 | 722 | static void |
b700eeb4 DM |
723 | probe_helpers_for_progtype(enum bpf_prog_type prog_type, |
724 | const char *prog_type_str, bool supported_type, | |
e3450b79 | 725 | const char *define_prefix, __u32 ifindex) |
2d3ea5e8 | 726 | { |
2d3ea5e8 QM |
727 | char feat_name[128]; |
728 | unsigned int id; | |
b06a92a1 | 729 | bool probe_res = false; |
2d3ea5e8 | 730 | |
f9499fed QM |
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 | ||
2d3ea5e8 | 741 | if (json_output) { |
b700eeb4 | 742 | sprintf(feat_name, "%s_available_helpers", prog_type_str); |
2d3ea5e8 QM |
743 | jsonw_name(json_wtr, feat_name); |
744 | jsonw_start_array(json_wtr); | |
d267cff4 | 745 | } else if (!define_prefix) { |
2d3ea5e8 | 746 | printf("eBPF helpers supported for program type %s:", |
b700eeb4 | 747 | prog_type_str); |
2d3ea5e8 QM |
748 | } |
749 | ||
750 | for (id = 1; id < ARRAY_SIZE(helper_name); id++) { | |
368cb0e7 MR |
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: | |
4190c299 | 756 | case BPF_FUNC_trace_vprintk: |
368cb0e7 MR |
757 | case BPF_FUNC_probe_write_user: |
758 | if (!full_mode) | |
759 | continue; | |
760 | /* fallthrough */ | |
761 | default: | |
b06a92a1 | 762 | probe_res |= probe_helper_for_progtype(prog_type, supported_type, |
b700eeb4 | 763 | define_prefix, id, prog_type_str, |
368cb0e7 | 764 | ifindex); |
2d3ea5e8 QM |
765 | } |
766 | } | |
767 | ||
768 | if (json_output) | |
769 | jsonw_end_array(json_wtr); | |
b06a92a1 | 770 | else if (!define_prefix) { |
2d3ea5e8 | 771 | printf("\n"); |
b06a92a1 ML |
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 | ||
2d3ea5e8 QM |
781 | } |
782 | ||
b22bf1b9 PC |
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 | ||
e967a20a AN |
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) | |
2faef64a | 813 | { |
e967a20a | 814 | struct bpf_insn insns[BPF_MAXINSNS + 1]; |
b22bf1b9 | 815 | int i; |
e967a20a AN |
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 | ||
b22bf1b9 PC |
821 | probe_misc_feature(insns, ARRAY_SIZE(insns), |
822 | define_prefix, ifindex, | |
823 | "have_large_insn_limit", | |
2faef64a | 824 | "Large program size limit", |
b22bf1b9 | 825 | "LARGE_INSN_LIMIT"); |
2faef64a MR |
826 | } |
827 | ||
c04fb2b0 PC |
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 | "have_bounded_loops", | |
845 | "Bounded loop support", | |
846 | "BOUNDED_LOOPS"); | |
847 | } | |
848 | ||
0fd800b2 PC |
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 | "have_v2_isa_extension", | |
866 | "ISA extension v2", | |
867 | "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 | "have_v3_isa_extension", | |
887 | "ISA extension v3", | |
888 | "V3_ISA_EXTENSION"); | |
889 | } | |
890 | ||
6b52ca44 MR |
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: | |
6b52ca44 MR |
897 | print_start_section("system_config", |
898 | "Scanning system configuration...", | |
0ee52c0f DB |
899 | "/*** 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("/* procfs not mounted, skipping related probes */"); | |
910 | } | |
6b52ca44 | 911 | } |
0ee52c0f | 912 | probe_kernel_image_config(define_prefix); |
6b52ca44 MR |
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("syscall_config", | |
925 | "Scanning system call availability...", | |
926 | "/*** 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 | { | |
b700eeb4 DM |
938 | unsigned int prog_type = BPF_PROG_TYPE_UNSPEC; |
939 | const char *prog_type_str; | |
6b52ca44 MR |
940 | |
941 | print_start_section("program_types", | |
942 | "Scanning eBPF program types...", | |
943 | "/*** eBPF program types ***/", | |
944 | define_prefix); | |
945 | ||
b700eeb4 DM |
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 | } | |
6b52ca44 MR |
956 | |
957 | print_end_section(); | |
958 | } | |
959 | ||
960 | static void section_map_types(const char *define_prefix, __u32 ifindex) | |
961 | { | |
2e98964b DM |
962 | unsigned int map_type = BPF_MAP_TYPE_UNSPEC; |
963 | const char *map_type_str; | |
6b52ca44 MR |
964 | |
965 | print_start_section("map_types", | |
966 | "Scanning eBPF map types...", | |
967 | "/*** eBPF map types ***/", | |
968 | define_prefix); | |
969 | ||
2e98964b DM |
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 | } | |
6b52ca44 MR |
979 | |
980 | print_end_section(); | |
981 | } | |
982 | ||
983 | static void | |
e3450b79 | 984 | section_helpers(bool *supported_types, const char *define_prefix, __u32 ifindex) |
6b52ca44 | 985 | { |
b700eeb4 DM |
986 | unsigned int prog_type = BPF_PROG_TYPE_UNSPEC; |
987 | const char *prog_type_str; | |
6b52ca44 MR |
988 | |
989 | print_start_section("helpers", | |
990 | "Scanning eBPF helper functions...", | |
991 | "/*** 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); | |
b700eeb4 DM |
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_types[prog_type], | |
1018 | define_prefix, | |
e3450b79 | 1019 | ifindex); |
b700eeb4 | 1020 | } |
6b52ca44 MR |
1021 | |
1022 | print_end_section(); | |
1023 | } | |
1024 | ||
1025 | static void section_misc(const char *define_prefix, __u32 ifindex) | |
1026 | { | |
1027 | print_start_section("misc", | |
1028 | "Scanning miscellaneous eBPF features...", | |
1029 | "/*** eBPF misc features ***/", | |
1030 | define_prefix); | |
1031 | probe_large_insn_limit(define_prefix, ifindex); | |
c04fb2b0 | 1032 | probe_bounded_loops(define_prefix, ifindex); |
0fd800b2 PC |
1033 | probe_v2_isa_extension(define_prefix, ifindex); |
1034 | probe_v3_isa_extension(define_prefix, ifindex); | |
6b52ca44 MR |
1035 | print_end_section(); |
1036 | } | |
1037 | ||
73a4f040 QM |
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 | ||
cf9bf714 QM |
1043 | static int handle_perms(void) |
1044 | { | |
0b3b9ca3 | 1045 | #ifdef USE_LIBCAP |
73a4f040 QM |
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; | |
cf9bf714 QM |
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 | ||
73a4f040 QM |
1072 | #ifdef CAP_BPF |
1073 | if (CAP_IS_SUPPORTED(CAP_BPF)) | |
1074 | cap_sys_admin_only = false; | |
1075 | #endif | |
cf9bf714 | 1076 | |
73a4f040 QM |
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; | |
cf9bf714 QM |
1098 | } |
1099 | ||
73a4f040 QM |
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)) { | |
cf9bf714 QM |
1103 | /* We are all good, exit now */ |
1104 | res = 0; | |
1105 | goto exit_free; | |
1106 | } | |
1107 | ||
73a4f040 QM |
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), | |
1e6f5dcc | 1115 | #ifdef CAP_BPF |
73a4f040 QM |
1116 | capability_msg(bpf_caps, 1), |
1117 | capability_msg(bpf_caps, 2), | |
1e6f5dcc IR |
1118 | capability_msg(bpf_caps, 3) |
1119 | #else | |
1120 | "", "", "", "", "", "" | |
1121 | #endif /* CAP_BPF */ | |
1122 | ); | |
73a4f040 QM |
1123 | goto exit_free; |
1124 | } | |
cf9bf714 | 1125 | |
73a4f040 QM |
1126 | /* if (run_as_unprivileged && nb_bpf_caps > 0), drop capabilities. */ |
1127 | if (cap_set_flag(caps, CAP_EFFECTIVE, nb_bpf_caps, cap_list, | |
cf9bf714 | 1128 | CAP_CLEAR)) { |
73a4f040 | 1129 | p_err("bug: failed to clear capabilities: %s", strerror(errno)); |
cf9bf714 QM |
1130 | goto exit_free; |
1131 | } | |
1132 | ||
1133 | if (cap_set_proc(caps)) { | |
73a4f040 | 1134 | p_err("failed to drop capabilities: %s", strerror(errno)); |
cf9bf714 QM |
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; | |
0b3b9ca3 | 1148 | #else |
73a4f040 | 1149 | /* Detection assumes user has specific privileges. |
54c93977 | 1150 | * We do not use libcap so let's approximate, and restrict usage to |
0b3b9ca3 QM |
1151 | * root user only. |
1152 | */ | |
1153 | if (geteuid()) { | |
1154 | p_err("full feature probing requires root privileges"); | |
1155 | return -1; | |
1156 | } | |
1157 | ||
1158 | return 0; | |
1159 | #endif /* USE_LIBCAP */ | |
cf9bf714 QM |
1160 | } |
1161 | ||
49eb7ab3 QM |
1162 | static int do_probe(int argc, char **argv) |
1163 | { | |
1164 | enum probe_component target = COMPONENT_UNSPEC; | |
d267cff4 | 1165 | const char *define_prefix = NULL; |
1bf4b058 | 1166 | bool supported_types[128] = {}; |
f9499fed | 1167 | __u32 ifindex = 0; |
f9499fed | 1168 | char *ifname; |
49eb7ab3 | 1169 | |
6b4384ff QM |
1170 | set_max_rlimit(); |
1171 | ||
49eb7ab3 QM |
1172 | while (argc) { |
1173 | if (is_prefix(*argv, "kernel")) { | |
1174 | if (target != COMPONENT_UNSPEC) { | |
1175 | p_err("component to probe already specified"); | |
1176 | return -1; | |
1177 | } | |
1178 | target = COMPONENT_KERNEL; | |
1179 | NEXT_ARG(); | |
f9499fed QM |
1180 | } else if (is_prefix(*argv, "dev")) { |
1181 | NEXT_ARG(); | |
1182 | ||
1183 | if (target != COMPONENT_UNSPEC || ifindex) { | |
1184 | p_err("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 | } | |
368cb0e7 MR |
1198 | } else if (is_prefix(*argv, "full")) { |
1199 | full_mode = true; | |
1200 | NEXT_ARG(); | |
d267cff4 QM |
1201 | } else if (is_prefix(*argv, "macros") && !define_prefix) { |
1202 | define_prefix = ""; | |
1203 | NEXT_ARG(); | |
1204 | } else if (is_prefix(*argv, "prefix")) { | |
1205 | if (!define_prefix) { | |
1206 | p_err("'prefix' argument can only be use after 'macros'"); | |
1207 | return -1; | |
1208 | } | |
1209 | if (strcmp(define_prefix, "")) { | |
1210 | p_err("'prefix' already defined"); | |
1211 | return -1; | |
1212 | } | |
1213 | NEXT_ARG(); | |
1214 | ||
1215 | if (!REQ_ARGS(1)) | |
1216 | return -1; | |
1217 | define_prefix = GET_ARG(); | |
cf9bf714 | 1218 | } else if (is_prefix(*argv, "unprivileged")) { |
0b3b9ca3 | 1219 | #ifdef USE_LIBCAP |
cf9bf714 QM |
1220 | run_as_unprivileged = true; |
1221 | NEXT_ARG(); | |
0b3b9ca3 QM |
1222 | #else |
1223 | p_err("unprivileged run not supported, recompile bpftool with libcap"); | |
1224 | return -1; | |
1225 | #endif | |
49eb7ab3 | 1226 | } else { |
f9499fed | 1227 | p_err("expected no more arguments, 'kernel', 'dev', 'macros' or 'prefix', got: '%s'?", |
49eb7ab3 QM |
1228 | *argv); |
1229 | return -1; | |
1230 | } | |
1231 | } | |
1232 | ||
73a4f040 | 1233 | /* Full feature detection requires specific privileges. |
cf9bf714 QM |
1234 | * Let's approximate, and warn if user is not root. |
1235 | */ | |
1236 | if (handle_perms()) | |
1237 | return -1; | |
1238 | ||
d267cff4 QM |
1239 | if (json_output) { |
1240 | define_prefix = NULL; | |
49eb7ab3 | 1241 | jsonw_start_object(json_wtr); |
d267cff4 | 1242 | } |
49eb7ab3 | 1243 | |
6b52ca44 MR |
1244 | section_system_config(target, define_prefix); |
1245 | if (!section_syscall_config(define_prefix)) | |
1bf4b058 QM |
1246 | /* bpf() syscall unavailable, don't probe other BPF features */ |
1247 | goto exit_close_json; | |
6b52ca44 MR |
1248 | section_program_types(supported_types, define_prefix, ifindex); |
1249 | section_map_types(define_prefix, ifindex); | |
e3450b79 | 1250 | section_helpers(supported_types, define_prefix, ifindex); |
6b52ca44 | 1251 | section_misc(define_prefix, ifindex); |
2faef64a | 1252 | |
1bf4b058 | 1253 | exit_close_json: |
6b52ca44 | 1254 | if (json_output) |
49eb7ab3 QM |
1255 | /* End root object */ |
1256 | jsonw_end_object(json_wtr); | |
49eb7ab3 QM |
1257 | |
1258 | return 0; | |
1259 | } | |
1260 | ||
27b3f705 QM |
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 | ||
990a6194 | 1269 | static int do_list_builtins(int argc, char **argv) |
27b3f705 QM |
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(*argv, "prog_types")) { | |
1278 | get_name = (const char *(*)(unsigned int))libbpf_bpf_prog_type_str; | |
1279 | } else if (is_prefix(*argv, "map_types")) { | |
1280 | get_name = (const char *(*)(unsigned int))libbpf_bpf_map_type_str; | |
1281 | } else if (is_prefix(*argv, "attach_types")) { | |
1282 | get_name = (const char *(*)(unsigned int))libbpf_bpf_attach_type_str; | |
1283 | } else if (is_prefix(*argv, "link_types")) { | |
1284 | get_name = (const char *(*)(unsigned int))libbpf_bpf_link_type_str; | |
1285 | } else if (is_prefix(*argv, "helpers")) { | |
1286 | get_name = get_helper_name; | |
1287 | } else { | |
1288 | p_err("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(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(json_wtr, name); | |
1303 | else | |
1304 | printf("%s\n", name); | |
1305 | } | |
1306 | ||
1307 | if (json_output) | |
1308 | jsonw_end_array(json_wtr); /* root array */ | |
1309 | ||
1310 | return 0; | |
1311 | } | |
1312 | ||
49eb7ab3 QM |
1313 | static int do_help(int argc, char **argv) |
1314 | { | |
1315 | if (json_output) { | |
1316 | jsonw_null(json_wtr); | |
1317 | return 0; | |
1318 | } | |
1319 | ||
1320 | fprintf(stderr, | |
90040351 | 1321 | "Usage: %1$s %2$s probe [COMPONENT] [full] [unprivileged] [macros [prefix PREFIX]]\n" |
990a6194 | 1322 | " %1$s %2$s list_builtins GROUP\n" |
90040351 | 1323 | " %1$s %2$s help\n" |
f9499fed QM |
1324 | "\n" |
1325 | " COMPONENT := { kernel | dev NAME }\n" | |
27b3f705 | 1326 | " GROUP := { prog_types | map_types | attach_types | link_types | helpers }\n" |
c07ba629 | 1327 | " " HELP_SPEC_OPTIONS " }\n" |
49eb7ab3 | 1328 | "", |
90040351 | 1329 | bin_name, argv[-2]); |
49eb7ab3 QM |
1330 | |
1331 | return 0; | |
1332 | } | |
1333 | ||
1334 | static const struct cmd cmds[] = { | |
990a6194 QM |
1335 | { "probe", do_probe }, |
1336 | { "list_builtins", do_list_builtins }, | |
1337 | { "help", do_help }, | |
49eb7ab3 QM |
1338 | { 0 } |
1339 | }; | |
1340 | ||
1341 | int do_feature(int argc, char **argv) | |
1342 | { | |
1343 | return cmd_select(cmds, argc, argv, do_help); | |
1344 | } |