2020-02-18 19:02:55

by Michal Rostecki

[permalink] [raw]
Subject: [PATCH bpf-next 0/6] bpftool: Allow to select sections and filter probes

This patch series extend the "bpftool feature" subcommand with the
new positional arguments:

- "section", which allows to select a specific section of probes (i.e.
"system_config", "program_types", "map_types");
- "filter_in", which allows to select only probes which matches the
given regex pattern;
- "filter_out", which allows to filter out probes which do not match the
given regex pattern.

The main motivation behind those changes is ability the fact that some
probes (for example those related to "trace" or "write_user" helpers)
emit dmesg messages which might be confusing for people who are running
on production environments. For details see the Cilium issue[0].

[0] https://github.com/cilium/cilium/issues/10048

Michal Rostecki (6):
bpftool: Move out sections to separate functions
bpftool: Allow to select a specific section to probe
bpftool: Add arguments for filtering in and filtering out probes
bpftool: Update documentation of "bpftool feature" command
bpftool: Update bash completion for "bpftool feature" command
selftests/bpf: Add test for "bpftool feature" command

.../bpftool/Documentation/bpftool-feature.rst | 37 +-
tools/bpf/bpftool/bash-completion/bpftool | 32 +-
tools/bpf/bpftool/feature.c | 592 +++++++++++++-----
tools/testing/selftests/.gitignore | 5 +-
tools/testing/selftests/bpf/Makefile | 3 +-
tools/testing/selftests/bpf/test_bpftool.py | 294 +++++++++
tools/testing/selftests/bpf/test_bpftool.sh | 5 +
7 files changed, 811 insertions(+), 157 deletions(-)
create mode 100644 tools/testing/selftests/bpf/test_bpftool.py
create mode 100755 tools/testing/selftests/bpf/test_bpftool.sh

--
2.25.0


2020-02-18 19:02:56

by Michal Rostecki

[permalink] [raw]
Subject: [PATCH bpf-next 1/6] bpftool: Move out sections to separate functions

Remove all calls of print_end_then_start_section function and for loops
out from the do_probe function. Instead, provide separate functions for
each section (like i.e. section_helpers) which are called in do_probe.

Next changes are going to allow probing chosen subsets of features.
This change will make them more readable.

Signed-off-by: Michal Rostecki <[email protected]>
---
tools/bpf/bpftool/feature.c | 219 +++++++++++++++++++++---------------
1 file changed, 126 insertions(+), 93 deletions(-)

diff --git a/tools/bpf/bpftool/feature.c b/tools/bpf/bpftool/feature.c
index 941873d778d8..345e4a2b4f53 100644
--- a/tools/bpf/bpftool/feature.c
+++ b/tools/bpf/bpftool/feature.c
@@ -112,18 +112,12 @@ print_start_section(const char *json_title, const char *plain_title,
}
}

-static void
-print_end_then_start_section(const char *json_title, const char *plain_title,
- const char *define_comment,
- const char *define_prefix)
+static void print_end_section(void)
{
if (json_output)
jsonw_end_object(json_wtr);
else
printf("\n");
-
- print_start_section(json_title, plain_title, define_comment,
- define_prefix);
}

/* Probing functions */
@@ -584,13 +578,130 @@ probe_large_insn_limit(const char *define_prefix, __u32 ifindex)
res, define_prefix);
}

+static void
+section_system_config(enum probe_component target, const char *define_prefix)
+{
+ switch (target) {
+ case COMPONENT_KERNEL:
+ case COMPONENT_UNSPEC:
+ if (define_prefix)
+ break;
+
+ print_start_section("system_config",
+ "Scanning system configuration...",
+ NULL, /* define_comment never used here */
+ NULL); /* define_prefix always NULL here */
+ if (check_procfs()) {
+ probe_unprivileged_disabled();
+ probe_jit_enable();
+ probe_jit_harden();
+ probe_jit_kallsyms();
+ probe_jit_limit();
+ } else {
+ p_info("/* procfs not mounted, skipping related probes */");
+ }
+ probe_kernel_image_config();
+ print_end_section();
+ break;
+ default:
+ break;
+ }
+}
+
+static bool section_syscall_config(const char *define_prefix)
+{
+ bool res;
+
+ print_start_section("syscall_config",
+ "Scanning system call availability...",
+ "/*** System call availability ***/",
+ define_prefix);
+ res = probe_bpf_syscall(define_prefix);
+ print_end_section();
+
+ return res;
+}
+
+static void
+section_program_types(bool *supported_types, const char *define_prefix,
+ __u32 ifindex)
+{
+ unsigned int i;
+
+ print_start_section("program_types",
+ "Scanning eBPF program types...",
+ "/*** eBPF program types ***/",
+ define_prefix);
+
+ for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
+ probe_prog_type(i, supported_types, define_prefix, ifindex);
+
+ print_end_section();
+}
+
+static void section_map_types(const char *define_prefix, __u32 ifindex)
+{
+ unsigned int i;
+
+ print_start_section("map_types",
+ "Scanning eBPF map types...",
+ "/*** eBPF map types ***/",
+ define_prefix);
+
+ for (i = BPF_MAP_TYPE_UNSPEC + 1; i < map_type_name_size; i++)
+ probe_map_type(i, define_prefix, ifindex);
+
+ print_end_section();
+}
+
+static void
+section_helpers(bool *supported_types, const char *define_prefix, __u32 ifindex)
+{
+ unsigned int i;
+
+ print_start_section("helpers",
+ "Scanning eBPF helper functions...",
+ "/*** eBPF helper functions ***/",
+ define_prefix);
+
+ if (define_prefix)
+ printf("/*\n"
+ " * Use %sHAVE_PROG_TYPE_HELPER(prog_type_name, helper_name)\n"
+ " * to determine if <helper_name> is available for <prog_type_name>,\n"
+ " * e.g.\n"
+ " * #if %sHAVE_PROG_TYPE_HELPER(xdp, bpf_redirect)\n"
+ " * // do stuff with this helper\n"
+ " * #elif\n"
+ " * // use a workaround\n"
+ " * #endif\n"
+ " */\n"
+ "#define %sHAVE_PROG_TYPE_HELPER(prog_type, helper) \\\n"
+ " %sBPF__PROG_TYPE_ ## prog_type ## __HELPER_ ## helper\n",
+ define_prefix, define_prefix, define_prefix,
+ define_prefix);
+ for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
+ probe_helpers_for_progtype(i, supported_types[i],
+ define_prefix, ifindex);
+
+ print_end_section();
+}
+
+static void section_misc(const char *define_prefix, __u32 ifindex)
+{
+ print_start_section("misc",
+ "Scanning miscellaneous eBPF features...",
+ "/*** eBPF misc features ***/",
+ define_prefix);
+ probe_large_insn_limit(define_prefix, ifindex);
+ print_end_section();
+}
+
static int do_probe(int argc, char **argv)
{
enum probe_component target = COMPONENT_UNSPEC;
const char *define_prefix = NULL;
bool supported_types[128] = {};
__u32 ifindex = 0;
- unsigned int i;
char *ifname;

/* Detection assumes user has sufficient privileges (CAP_SYS_ADMIN).
@@ -658,97 +769,19 @@ static int do_probe(int argc, char **argv)
jsonw_start_object(json_wtr);
}

- switch (target) {
- case COMPONENT_KERNEL:
- case COMPONENT_UNSPEC:
- if (define_prefix)
- break;
-
- print_start_section("system_config",
- "Scanning system configuration...",
- NULL, /* define_comment never used here */
- NULL); /* define_prefix always NULL here */
- if (check_procfs()) {
- probe_unprivileged_disabled();
- probe_jit_enable();
- probe_jit_harden();
- probe_jit_kallsyms();
- probe_jit_limit();
- } else {
- p_info("/* procfs not mounted, skipping related probes */");
- }
- probe_kernel_image_config();
- if (json_output)
- jsonw_end_object(json_wtr);
- else
- printf("\n");
- break;
- default:
- break;
- }
-
- print_start_section("syscall_config",
- "Scanning system call availability...",
- "/*** System call availability ***/",
- define_prefix);
-
- if (!probe_bpf_syscall(define_prefix))
+ section_system_config(target, define_prefix);
+ if (!section_syscall_config(define_prefix))
/* bpf() syscall unavailable, don't probe other BPF features */
goto exit_close_json;
-
- print_end_then_start_section("program_types",
- "Scanning eBPF program types...",
- "/*** eBPF program types ***/",
- define_prefix);
-
- for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
- probe_prog_type(i, supported_types, define_prefix, ifindex);
-
- print_end_then_start_section("map_types",
- "Scanning eBPF map types...",
- "/*** eBPF map types ***/",
- define_prefix);
-
- for (i = BPF_MAP_TYPE_UNSPEC + 1; i < map_type_name_size; i++)
- probe_map_type(i, define_prefix, ifindex);
-
- print_end_then_start_section("helpers",
- "Scanning eBPF helper functions...",
- "/*** eBPF helper functions ***/",
- define_prefix);
-
- if (define_prefix)
- printf("/*\n"
- " * Use %sHAVE_PROG_TYPE_HELPER(prog_type_name, helper_name)\n"
- " * to determine if <helper_name> is available for <prog_type_name>,\n"
- " * e.g.\n"
- " * #if %sHAVE_PROG_TYPE_HELPER(xdp, bpf_redirect)\n"
- " * // do stuff with this helper\n"
- " * #elif\n"
- " * // use a workaround\n"
- " * #endif\n"
- " */\n"
- "#define %sHAVE_PROG_TYPE_HELPER(prog_type, helper) \\\n"
- " %sBPF__PROG_TYPE_ ## prog_type ## __HELPER_ ## helper\n",
- define_prefix, define_prefix, define_prefix,
- define_prefix);
- for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
- probe_helpers_for_progtype(i, supported_types[i],
- define_prefix, ifindex);
-
- print_end_then_start_section("misc",
- "Scanning miscellaneous eBPF features...",
- "/*** eBPF misc features ***/",
- define_prefix);
- probe_large_insn_limit(define_prefix, ifindex);
+ section_program_types(supported_types, define_prefix, ifindex);
+ section_map_types(define_prefix, ifindex);
+ section_helpers(supported_types, define_prefix, ifindex);
+ section_misc(define_prefix, ifindex);

exit_close_json:
- if (json_output) {
- /* End current "section" of probes */
- jsonw_end_object(json_wtr);
+ if (json_output)
/* End root object */
jsonw_end_object(json_wtr);
- }

return 0;
}
--
2.25.0

2020-02-18 19:03:03

by Michal Rostecki

[permalink] [raw]
Subject: [PATCH bpf-next 3/6] bpftool: Add arguments for filtering in and filtering out probes

Add positional arguments "filter_in" and "filter_out" to the feature
command of bpftool. If "filter_in" is defined, bpftool is going to
perform and print only checks which match the given pattern. If
"filter_out" is defined, bpftool will not perform and print checks which
match the given pattern.

Signed-off-by: Michal Rostecki <[email protected]>
---
tools/bpf/bpftool/feature.c | 324 ++++++++++++++++++++++++++++--------
1 file changed, 256 insertions(+), 68 deletions(-)

diff --git a/tools/bpf/bpftool/feature.c b/tools/bpf/bpftool/feature.c
index cfba0faf148f..f10e928d18c8 100644
--- a/tools/bpf/bpftool/feature.c
+++ b/tools/bpf/bpftool/feature.c
@@ -3,6 +3,7 @@

#include <ctype.h>
#include <errno.h>
+#include <regex.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
@@ -64,6 +65,55 @@ static void uppercase(char *str, size_t len)
str[i] = toupper(str[i]);
}

+/* Filtering utility functions */
+
+static bool
+check_filters(const char *name, regex_t *filter_in, regex_t *filter_out)
+{
+ char err_buf[100];
+ int ret;
+
+ /* Do not probe if filter_in was defined and string does not match
+ * against the pattern.
+ */
+ if (filter_in) {
+ ret = regexec(filter_in, name, 0, NULL, 0);
+ switch (ret) {
+ case 0:
+ break;
+ case REG_NOMATCH:
+ return false;
+ default:
+ regerror(ret, filter_in, err_buf, ARRAY_SIZE(err_buf));
+ p_err("could not match regex: %s", err_buf);
+ free(filter_in);
+ free(filter_out);
+ exit(1);
+ }
+ }
+
+ /* Do not probe if filter_out was defined and string matches against the
+ * pattern.
+ */
+ if (filter_out) {
+ ret = regexec(filter_out, name, 0, NULL, 0);
+ switch (ret) {
+ case 0:
+ return false;
+ case REG_NOMATCH:
+ break;
+ default:
+ regerror(ret, filter_out, err_buf, ARRAY_SIZE(err_buf));
+ p_err("could not match regex: %s", err_buf);
+ free(filter_in);
+ free(filter_out);
+ exit(1);
+ }
+ }
+
+ return true;
+}
+
/* Printing utility functions */

static void
@@ -79,11 +129,16 @@ print_bool_feature(const char *feat_name, const char *plain_name,
printf("%s is %savailable\n", plain_name, res ? "" : "NOT ");
}

-static void print_kernel_option(const char *name, const char *value)
+static void
+print_kernel_option(const char *name, const char *value, regex_t *filter_in,
+ regex_t *filter_out)
{
char *endptr;
int res;

+ if (!check_filters(name, filter_in, filter_out))
+ return;
+
/* No support for C-style ouptut */

if (json_output) {
@@ -154,15 +209,19 @@ static int read_procfs(const char *path)
return res;
}

-static void probe_unprivileged_disabled(void)
+static void probe_unprivileged_disabled(regex_t *filter_in, regex_t *filter_out)
{
+ const char *feat_name = "unprivileged_bpf_disabled";
int res;

/* No support for C-style ouptut */

+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;
+
res = read_procfs("/proc/sys/kernel/unprivileged_bpf_disabled");
if (json_output) {
- jsonw_int_field(json_wtr, "unprivileged_bpf_disabled", res);
+ jsonw_int_field(json_wtr, feat_name, res);
} else {
switch (res) {
case 0:
@@ -180,15 +239,19 @@ static void probe_unprivileged_disabled(void)
}
}

-static void probe_jit_enable(void)
+static void probe_jit_enable(regex_t *filter_in, regex_t *filter_out)
{
+ const char *feat_name = "bpf_jit_enable";
int res;

/* No support for C-style ouptut */

+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;
+
res = read_procfs("/proc/sys/net/core/bpf_jit_enable");
if (json_output) {
- jsonw_int_field(json_wtr, "bpf_jit_enable", res);
+ jsonw_int_field(json_wtr, feat_name, res);
} else {
switch (res) {
case 0:
@@ -210,15 +273,19 @@ static void probe_jit_enable(void)
}
}

-static void probe_jit_harden(void)
+static void probe_jit_harden(regex_t *filter_in, regex_t *filter_out)
{
+ const char *feat_name = "bpf_jit_harden";
int res;

/* No support for C-style ouptut */

+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;
+
res = read_procfs("/proc/sys/net/core/bpf_jit_harden");
if (json_output) {
- jsonw_int_field(json_wtr, "bpf_jit_harden", res);
+ jsonw_int_field(json_wtr, feat_name, res);
} else {
switch (res) {
case 0:
@@ -240,15 +307,19 @@ static void probe_jit_harden(void)
}
}

-static void probe_jit_kallsyms(void)
+static void probe_jit_kallsyms(regex_t *filter_in, regex_t *filter_out)
{
+ const char *feat_name = "bpf_jit_kallsyms";
int res;

/* No support for C-style ouptut */

+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;
+
res = read_procfs("/proc/sys/net/core/bpf_jit_kallsyms");
if (json_output) {
- jsonw_int_field(json_wtr, "bpf_jit_kallsyms", res);
+ jsonw_int_field(json_wtr, feat_name, res);
} else {
switch (res) {
case 0:
@@ -266,15 +337,19 @@ static void probe_jit_kallsyms(void)
}
}

-static void probe_jit_limit(void)
+static void probe_jit_limit(regex_t *filter_in, regex_t *filter_out)
{
+ const char *feat_name = "bpf_jit_limit";
int res;

/* No support for C-style ouptut */

+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;
+
res = read_procfs("/proc/sys/net/core/bpf_jit_limit");
if (json_output) {
- jsonw_int_field(json_wtr, "bpf_jit_limit", res);
+ jsonw_int_field(json_wtr, feat_name, res);
} else {
switch (res) {
case -1:
@@ -314,7 +389,8 @@ static bool read_next_kernel_config_option(gzFile file, char *buf, size_t n,
return false;
}

-static void probe_kernel_image_config(void)
+static void
+probe_kernel_image_config(regex_t *filter_in, regex_t *filter_out)
{
static const char * const options[] = {
/* Enable BPF */
@@ -438,23 +514,31 @@ static void probe_kernel_image_config(void)
gzclose(file);

for (i = 0; i < ARRAY_SIZE(options); i++) {
- print_kernel_option(options[i], values[i]);
+ print_kernel_option(options[i], values[i], filter_in,
+ filter_out);
free(values[i]);
}
}

static bool
-probe_bpf_syscall(bool print_syscall_config, const char *define_prefix)
+probe_bpf_syscall(bool print_syscall_config, const char *define_prefix,
+ regex_t *filter_in, regex_t *filter_out)
{
+ const char *feat_name = "have_bpf_syscall";
+ const char *plain_desc = "bpf() syscall";
+ const char *define_name = "BPF_SYSCALL";
bool res;

bpf_load_program(BPF_PROG_TYPE_UNSPEC, NULL, 0, NULL, 0, NULL, 0);
res = (errno != ENOSYS);

+ if (!check_filters(feat_name, filter_in, filter_out))
+ print_syscall_config = false;
+
if (print_syscall_config)
- print_bool_feature("have_bpf_syscall",
- "bpf() syscall",
- "BPF_SYSCALL",
+ print_bool_feature(feat_name,
+ plain_desc,
+ define_name,
res, define_prefix);

return res;
@@ -462,13 +546,19 @@ probe_bpf_syscall(bool print_syscall_config, const char *define_prefix)

static void
probe_prog_type(bool print_program_types, enum bpf_prog_type prog_type,
- bool *supported_types, const char *define_prefix, __u32 ifindex)
+ bool *supported_types, const char *define_prefix,
+ regex_t *filter_in, regex_t *filter_out, __u32 ifindex)
{
char feat_name[128], plain_desc[128], define_name[128];
const char *plain_comment = "eBPF program_type ";
size_t maxlen;
bool res;

+ sprintf(feat_name, "have_%s_prog_type", prog_type_name[prog_type]);
+ sprintf(define_name, "%s_prog_type", prog_type_name[prog_type]);
+ uppercase(define_name, sizeof(define_name));
+ sprintf(plain_desc, "%s%s", plain_comment, prog_type_name[prog_type]);
+
if (ifindex)
/* Only test offload-able program types */
switch (prog_type) {
@@ -489,10 +579,8 @@ probe_prog_type(bool print_program_types, enum bpf_prog_type prog_type,
return;
}

- sprintf(feat_name, "have_%s_prog_type", prog_type_name[prog_type]);
- sprintf(define_name, "%s_prog_type", prog_type_name[prog_type]);
- uppercase(define_name, sizeof(define_name));
- sprintf(plain_desc, "%s%s", plain_comment, prog_type_name[prog_type]);
+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;

if (print_program_types)
print_bool_feature(feat_name, plain_desc, define_name, res,
@@ -501,13 +589,21 @@ probe_prog_type(bool print_program_types, enum bpf_prog_type prog_type,

static void
probe_map_type(enum bpf_map_type map_type, const char *define_prefix,
- __u32 ifindex)
+ regex_t *filter_in, regex_t *filter_out, __u32 ifindex)
{
char feat_name[128], plain_desc[128], define_name[128];
const char *plain_comment = "eBPF map_type ";
size_t maxlen;
bool res;

+ sprintf(feat_name, "have_%s_map_type", map_type_name[map_type]);
+ sprintf(define_name, "%s_map_type", map_type_name[map_type]);
+ uppercase(define_name, sizeof(define_name));
+ sprintf(plain_desc, "%s%s", plain_comment, map_type_name[map_type]);
+
+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;
+
res = bpf_probe_map_type(map_type, ifindex);

maxlen = sizeof(plain_desc) - strlen(plain_comment) - 1;
@@ -516,23 +612,23 @@ probe_map_type(enum bpf_map_type map_type, const char *define_prefix,
return;
}

- sprintf(feat_name, "have_%s_map_type", map_type_name[map_type]);
- sprintf(define_name, "%s_map_type", map_type_name[map_type]);
- uppercase(define_name, sizeof(define_name));
- sprintf(plain_desc, "%s%s", plain_comment, map_type_name[map_type]);
print_bool_feature(feat_name, plain_desc, define_name, res,
define_prefix);
}

static void
probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type,
- const char *define_prefix, __u32 ifindex)
+ const char *define_prefix, regex_t *filter_in,
+ regex_t *filter_out, __u32 ifindex)
{
const char *ptype_name = prog_type_name[prog_type];
char feat_name[128];
unsigned int id;
bool res;

+ if (!check_filters(ptype_name, filter_in, filter_out))
+ return;
+
if (ifindex)
/* Only test helpers for offload-able program types */
switch (prog_type) {
@@ -553,6 +649,9 @@ probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type,
}

for (id = 1; id < ARRAY_SIZE(helper_name); id++) {
+ if (!check_filters(helper_name[id], filter_in, filter_out))
+ continue;
+
if (!supported_type)
res = false;
else
@@ -578,19 +677,27 @@ probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type,
}

static void
-probe_large_insn_limit(const char *define_prefix, __u32 ifindex)
+probe_large_insn_limit(const char *define_prefix, regex_t *filter_in,
+ regex_t *filter_out, __u32 ifindex)
{
+ const char *plain_desc = "Large program size limit";
+ const char *feat_name = "have_large_insn_limit";
+ const char *define_name = "LARGE_INSN_LIMIT";
bool res;

+ if (!check_filters(feat_name, filter_in, filter_out))
+ return;
+
res = bpf_probe_large_insn_limit(ifindex);
- print_bool_feature("have_large_insn_limit",
- "Large program size limit",
- "LARGE_INSN_LIMIT",
+ print_bool_feature(feat_name,
+ plain_desc,
+ define_name,
res, define_prefix);
}

static void
-section_system_config(enum probe_component target, const char *define_prefix)
+section_system_config(enum probe_component target, const char *define_prefix,
+ regex_t *filter_in, regex_t *filter_out)
{
switch (target) {
case COMPONENT_KERNEL:
@@ -603,15 +710,15 @@ section_system_config(enum probe_component target, const char *define_prefix)
NULL, /* define_comment never used here */
NULL); /* define_prefix always NULL here */
if (check_procfs()) {
- probe_unprivileged_disabled();
- probe_jit_enable();
- probe_jit_harden();
- probe_jit_kallsyms();
- probe_jit_limit();
+ probe_unprivileged_disabled(filter_in, filter_out);
+ probe_jit_enable(filter_in, filter_out);
+ probe_jit_harden(filter_in, filter_out);
+ probe_jit_kallsyms(filter_in, filter_out);
+ probe_jit_limit(filter_in, filter_out);
} else {
p_info("/* procfs not mounted, skipping related probes */");
}
- probe_kernel_image_config();
+ probe_kernel_image_config(filter_in, filter_out);
print_end_section();
break;
default:
@@ -620,7 +727,8 @@ section_system_config(enum probe_component target, const char *define_prefix)
}

static bool
-section_syscall_config(bool print_syscall_config, const char *define_prefix)
+section_syscall_config(bool print_syscall_config, const char *define_prefix,
+ regex_t *filter_in, regex_t *filter_out)
{
bool res;

@@ -629,7 +737,8 @@ section_syscall_config(bool print_syscall_config, const char *define_prefix)
"Scanning system call availability...",
"/*** System call availability ***/",
define_prefix);
- res = probe_bpf_syscall(print_syscall_config, define_prefix);
+ res = probe_bpf_syscall(print_syscall_config, define_prefix,
+ filter_in, filter_out);
if (print_syscall_config)
print_end_section();

@@ -638,7 +747,8 @@ section_syscall_config(bool print_syscall_config, const char *define_prefix)

static void
section_program_types(bool print_program_types, bool *supported_types,
- const char *define_prefix, __u32 ifindex)
+ const char *define_prefix, regex_t *filter_in,
+ regex_t *filter_out, __u32 ifindex)
{
unsigned int i;

@@ -650,13 +760,14 @@ section_program_types(bool print_program_types, bool *supported_types,

for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
probe_prog_type(print_program_types, i, supported_types,
- define_prefix, ifindex);
+ define_prefix, filter_in, filter_out, ifindex);

if (print_program_types)
print_end_section();
}

-static void section_map_types(const char *define_prefix, __u32 ifindex)
+static void section_map_types(const char *define_prefix, regex_t *filter_in,
+ regex_t *filter_out, __u32 ifindex)
{
unsigned int i;

@@ -666,13 +777,15 @@ static void section_map_types(const char *define_prefix, __u32 ifindex)
define_prefix);

for (i = BPF_MAP_TYPE_UNSPEC + 1; i < map_type_name_size; i++)
- probe_map_type(i, define_prefix, ifindex);
+ probe_map_type(i, define_prefix, filter_in, filter_out,
+ ifindex);

print_end_section();
}

static void
-section_helpers(bool *supported_types, const char *define_prefix, __u32 ifindex)
+section_helpers(bool *supported_types, const char *define_prefix,
+ regex_t *filter_in, regex_t *filter_out, __u32 ifindex)
{
unsigned int i;

@@ -698,18 +811,20 @@ section_helpers(bool *supported_types, const char *define_prefix, __u32 ifindex)
define_prefix);
for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
probe_helpers_for_progtype(i, supported_types[i],
- define_prefix, ifindex);
+ define_prefix, filter_in, filter_out,
+ ifindex);

print_end_section();
}

-static void section_misc(const char *define_prefix, __u32 ifindex)
+static void section_misc(const char *define_prefix, regex_t *filter_in,
+ regex_t *filter_out, __u32 ifindex)
{
print_start_section(SECTION_MISC,
"Scanning miscellaneous eBPF features...",
"/*** eBPF misc features ***/",
define_prefix);
- probe_large_insn_limit(define_prefix, ifindex);
+ probe_large_insn_limit(define_prefix, filter_in, filter_out, ifindex);
print_end_section();
}

@@ -721,6 +836,8 @@ static int do_probe(int argc, char **argv)
* should exit.
*/
bool print_syscall_config = false;
+ const char *filter_out_raw = NULL;
+ const char *filter_in_raw = NULL;
const char *define_prefix = NULL;
bool check_system_config = false;
/* Program types probes are needed if helper probes are going to be
@@ -734,9 +851,14 @@ static int do_probe(int argc, char **argv)
bool check_map_types = false;
bool check_helpers = false;
bool check_section = false;
+ regex_t *filter_out = NULL;
+ regex_t *filter_in = NULL;
bool check_misc = false;
+ char regerror_buf[100];
__u32 ifindex = 0;
char *ifname;
+ int reg_ret;
+ int ret = 0;

/* Detection assumes user has sufficient privileges (CAP_SYS_ADMIN).
* Let's approximate, and restrict usage to root user only.
@@ -752,7 +874,8 @@ static int do_probe(int argc, char **argv)
if (is_prefix(*argv, "kernel")) {
if (target != COMPONENT_UNSPEC) {
p_err("component to probe already specified");
- return -1;
+ ret = -1;
+ goto cleanup;
}
target = COMPONENT_KERNEL;
NEXT_ARG();
@@ -761,10 +884,13 @@ static int do_probe(int argc, char **argv)

if (target != COMPONENT_UNSPEC || ifindex) {
p_err("component to probe already specified");
- return -1;
+ ret = -1;
+ goto cleanup;
+ }
+ if (!REQ_ARGS(1)) {
+ ret = -1;
+ goto cleanup;
}
- if (!REQ_ARGS(1))
- return -1;

target = COMPONENT_DEVICE;
ifname = GET_ARG();
@@ -772,7 +898,8 @@ static int do_probe(int argc, char **argv)
if (!ifindex) {
p_err("unrecognized netdevice '%s': %s", ifname,
strerror(errno));
- return -1;
+ ret = -1;
+ goto cleanup;
}
} else if (is_prefix(*argv, "section")) {
check_section = true;
@@ -804,30 +931,82 @@ static int do_probe(int argc, char **argv)
SECTION_MAP_TYPES,
SECTION_HELPERS,
SECTION_MISC);
- return -1;
+ ret = -1;
+ goto cleanup;
+ }
+ NEXT_ARG();
+ } else if (is_prefix(*argv, "filter_in")) {
+ if (filter_in_raw) {
+ p_err("filter_in can be used only once");
+ ret = -1;
+ goto cleanup;
}
NEXT_ARG();
+ if (!REQ_ARGS(1)) {
+ ret = -1;
+ goto cleanup;
+ }
+ filter_in_raw = GET_ARG();
+
+ filter_in = malloc(sizeof(regex_t));
+ reg_ret = regcomp(filter_in, filter_in_raw, 0);
+ if (reg_ret) {
+ regerror(reg_ret, filter_in, regerror_buf,
+ ARRAY_SIZE(regerror_buf));
+ p_err("could not compile regex: %s",
+ regerror_buf);
+ ret = -1;
+ goto cleanup;
+ }
+ } else if (is_prefix(*argv, "filter_out")) {
+ if (filter_out_raw) {
+ p_err("filter_out can be used only once");
+ ret = -1;
+ goto cleanup;
+ }
+ NEXT_ARG();
+ if (!REQ_ARGS(1)) {
+ ret = -1;
+ goto cleanup;
+ }
+ filter_out_raw = GET_ARG();
+
+ filter_out = malloc(sizeof(regex_t));
+ reg_ret = regcomp(filter_out, filter_out_raw, 0);
+ if (reg_ret) {
+ regerror(reg_ret, filter_out, regerror_buf,
+ ARRAY_SIZE(regerror_buf));
+ p_err("could not compile regex: %s",
+ regerror_buf);
+ ret = -1;
+ goto cleanup;
+ }
} else if (is_prefix(*argv, "macros") && !define_prefix) {
define_prefix = "";
NEXT_ARG();
} else if (is_prefix(*argv, "prefix")) {
if (!define_prefix) {
p_err("'prefix' argument can only be use after 'macros'");
- return -1;
+ ret = -1;
+ goto cleanup;
}
if (strcmp(define_prefix, "")) {
p_err("'prefix' already defined");
- return -1;
+ ret = -1;
+ goto cleanup;
}
NEXT_ARG();

- if (!REQ_ARGS(1))
- return -1;
+ if (!REQ_ARGS(1)) {
+ ret = -1;
+ goto cleanup;
+ }
define_prefix = GET_ARG();
} else {
p_err("expected no more arguments, 'kernel', 'dev', 'macros' or 'prefix', got: '%s'?",
*argv);
- return -1;
+ ret = -1;
+ goto cleanup;
}
}

@@ -848,26 +1027,35 @@ static int do_probe(int argc, char **argv)
}

if (check_system_config)
- section_system_config(target, define_prefix);
- if (!section_syscall_config(print_syscall_config, define_prefix))
+ section_system_config(target, define_prefix, filter_in,
+ filter_out);
+ if (!section_syscall_config(print_syscall_config, define_prefix,
+ filter_in, filter_out))
/* bpf() syscall unavailable, don't probe other BPF features */
goto exit_close_json;
if (check_program_types)
section_program_types(print_program_types, supported_types,
- define_prefix, ifindex);
+ define_prefix, filter_in, filter_out,
+ ifindex);
if (check_map_types)
- section_map_types(define_prefix, ifindex);
+ section_map_types(define_prefix, filter_in, filter_out,
+ ifindex);
if (check_helpers)
- section_helpers(supported_types, define_prefix, ifindex);
+ section_helpers(supported_types, define_prefix, filter_in,
+ filter_out, ifindex);
if (check_misc)
- section_misc(define_prefix, ifindex);
+ section_misc(define_prefix, filter_in, filter_out, ifindex);

exit_close_json:
if (json_output)
/* End root object */
jsonw_end_object(json_wtr);

- return 0;
+cleanup:
+ free(filter_in);
+ free(filter_out);
+
+ return ret;
}

static int do_help(int argc, char **argv)
@@ -878,7 +1066,7 @@ static int do_help(int argc, char **argv)
}

fprintf(stderr,
- "Usage: %s %s probe [COMPONENT] [section SECTION] [macros [prefix PREFIX]]\n"
+ "Usage: %s %s probe [COMPONENT] [section SECTION] [filter_in PATTERN] [filter_out PATTERN] [macros [prefix PREFIX]]\n"
" %s %s help\n"
"\n"
" COMPONENT := { kernel | dev NAME }\n"
--
2.25.0

2020-02-18 19:03:06

by Michal Rostecki

[permalink] [raw]
Subject: [PATCH bpf-next 4/6] bpftool: Update documentation of "bpftool feature" command

Update documentation of "bpftool feature" command with information about
new arguments: "section", "filter_in" and "filter_out".

Signed-off-by: Michal Rostecki <[email protected]>
---
.../bpftool/Documentation/bpftool-feature.rst | 37 ++++++++++++++++---
1 file changed, 32 insertions(+), 5 deletions(-)

diff --git a/tools/bpf/bpftool/Documentation/bpftool-feature.rst b/tools/bpf/bpftool/Documentation/bpftool-feature.rst
index 4d08f35034a2..39b4c47e3c75 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-feature.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-feature.rst
@@ -19,19 +19,45 @@ SYNOPSIS
FEATURE COMMANDS
================

-| **bpftool** **feature probe** [*COMPONENT*] [**macros** [**prefix** *PREFIX*]]
+| **bpftool** **feature probe** [*COMPONENT*] [**section** *SECTION*] [**filter_in** *PATTERN*] [**filter_out** *PATTERN*] [**macros** [**prefix** *PREFIX*]]
| **bpftool** **feature help**
|
| *COMPONENT* := { **kernel** | **dev** *NAME* }
+| *SECTION* := { **system_config** | **syscall_config** | **program_types** | **map_types** | **helpers** | **misc** }

DESCRIPTION
===========
- **bpftool feature probe** [**kernel**] [**macros** [**prefix** *PREFIX*]]
+ **bpftool feature probe** [**kernel**] [**section** *SECTION*] [**filter_in** *PATTERN*] [**filter_out** *PATTERN*] [**macros** [**prefix** *PREFIX*]]
Probe the running kernel and dump a number of eBPF-related
parameters, such as availability of the **bpf()** system call,
JIT status, eBPF program types availability, eBPF helper
functions availability, and more.

+ If the **section** keyword is passed, only the specified
+ probes section will be checked and printed. The only probe
+ which is always going to be performed is **syscall_config**,
+ but if the other section was provided as an argument,
+ **syscall_config** check will perform silently without
+ printing the result and bpftool will exit if the **bpf()**
+ syscall is not available (because in that case performing
+ other checks relying on the **bpf()** system call does not
+ make sense).
+
+ If the **filter_in** keyword is passed, only checks with
+ names matching the given *PATTERN* are going the be printed
+ and performed.
+
+ If the **filter_out** keyword is passed, checks with names
+ matching the given *PATTERN* are not going to be printed and
+ performed.
+
+ Please refer to the **regex**\ (7) man page for details on
+ the syntax for *PATTERN*.
+
+ **filter_in** is executed before **filter_out** which means
+ that **filter_out** is always applied only on probes
+ selected by **filter_in** if both arguments are used together.
+
If the **macros** keyword (but not the **-j** option) is
passed, a subset of the output is dumped as a list of
**#define** macros that are ready to be included in a C
@@ -48,12 +74,13 @@ DESCRIPTION
**bpf_trace_printk**\ () or **bpf_probe_write_user**\ ()) may
print warnings to kernel logs.

- **bpftool feature probe dev** *NAME* [**macros** [**prefix** *PREFIX*]]
+ **bpftool feature probe dev** *NAME* [**section** *SECTION*] [**filter_in** *PATTERN*] [**filter_out** *PATTERN*] [**macros** [**prefix** *PREFIX*]]
Probe network device for supported eBPF features and dump
results to the console.

- The two keywords **macros** and **prefix** have the same
- role as when probing the kernel.
+ The keywords **section**, **filter_in**, **filter_out**,
+ **macros** and **prefix** have the same role as when probing
+ the kernel.

**bpftool feature help**
Print short help message.
--
2.25.0

2020-02-18 19:03:10

by Michal Rostecki

[permalink] [raw]
Subject: [PATCH bpf-next 5/6] bpftool: Update bash completion for "bpftool feature" command

Update bash completion for "bpftool feature" command with information
about new arguments: "section", "filter_id" and "filter_out".

Signed-off-by: Michal Rostecki <[email protected]>
---
tools/bpf/bpftool/bash-completion/bpftool | 32 +++++++++++++++++------
1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool
index 754d8395e451..ff8ac9bebdda 100644
--- a/tools/bpf/bpftool/bash-completion/bpftool
+++ b/tools/bpf/bpftool/bash-completion/bpftool
@@ -981,14 +981,30 @@ _bpftool()
feature)
case $command in
probe)
- [[ $prev == "prefix" ]] && return 0
- if _bpftool_search_list 'macros'; then
- COMPREPLY+=( $( compgen -W 'prefix' -- "$cur" ) )
- else
- COMPREPLY+=( $( compgen -W 'macros' -- "$cur" ) )
- fi
- _bpftool_one_of_list 'kernel dev'
- return 0
+ case $prev in
+ $command)
+ COMPREPLY+=( $( compgen -W 'kernel dev section filter_in filter_out macros' -- \
+ "$cur" ) )
+ return 0
+ ;;
+ section)
+ COMPREPLY+=( $( compgen -W 'system_config syscall_config program_types map_types helpers misc' -- \
+ "$cur" ) )
+ return 0
+ ;;
+ filter_in|filter_out|prefix)
+ return 0
+ ;;
+ macros)
+ COMPREPLY+=( $( compgen -W 'prefix' -- "$cur" ) )
+ return 0
+ ;;
+ *)
+ _bpftool_one_of_list 'kernel dev'
+ _bpftool_once_attr 'section filter_in filter_out macros'
+ return 0
+ ;;
+ esac
;;
*)
[[ $prev == $object ]] && \
--
2.25.0

2020-02-18 19:03:20

by Michal Rostecki

[permalink] [raw]
Subject: [PATCH bpf-next 6/6] selftests/bpf: Add test for "bpftool feature" command

Add Python module with tests for "bpftool feature" command which check
whether:

- probing kernel and network devices works
- "section" option selects sections properly
- "filter_in" and "filter_out" options filter results properly
- "macro" option generates C macros properly

Signed-off-by: Michal Rostecki <[email protected]>
---
tools/testing/selftests/.gitignore | 5 +-
tools/testing/selftests/bpf/Makefile | 3 +-
tools/testing/selftests/bpf/test_bpftool.py | 294 ++++++++++++++++++++
tools/testing/selftests/bpf/test_bpftool.sh | 5 +
4 files changed, 305 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/bpf/test_bpftool.py
create mode 100755 tools/testing/selftests/bpf/test_bpftool.sh

diff --git a/tools/testing/selftests/.gitignore b/tools/testing/selftests/.gitignore
index 61df01cdf0b2..304fdf1a21dc 100644
--- a/tools/testing/selftests/.gitignore
+++ b/tools/testing/selftests/.gitignore
@@ -3,4 +3,7 @@ gpiogpio-hammer
gpioinclude/
gpiolsgpio
tpm2/SpaceTest.log
-tpm2/*.pyc
+
+# Python bytecode and cache
+__pycache__/
+*.py[cod]
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 257a1aaaa37d..e7d822259c50 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -62,7 +62,8 @@ TEST_PROGS := test_kmod.sh \
test_tc_tunnel.sh \
test_tc_edt.sh \
test_xdping.sh \
- test_bpftool_build.sh
+ test_bpftool_build.sh \
+ test_bpftool.sh

TEST_PROGS_EXTENDED := with_addr.sh \
with_tunnels.sh \
diff --git a/tools/testing/selftests/bpf/test_bpftool.py b/tools/testing/selftests/bpf/test_bpftool.py
new file mode 100644
index 000000000000..e298dca5fdcf
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_bpftool.py
@@ -0,0 +1,294 @@
+# Copyright (c) 2020 SUSE LLC.
+#
+# This software is licensed under the GNU General License Version 2,
+# June 1991 as shown in the file COPYING in the top-level directory of this
+# source tree.
+#
+# THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
+# WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+# THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+import collections
+import functools
+import json
+import os
+import socket
+import subprocess
+import unittest
+
+
+# Add the source tree of bpftool and /usr/local/sbin to PATH
+cur_dir = os.path.dirname(os.path.realpath(__file__))
+bpftool_dir = os.path.abspath(os.path.join(cur_dir, "..", "..", "..", "..",
+ "tools", "bpf", "bpftool"))
+os.environ["PATH"] = bpftool_dir + ":/usr/local/sbin:" + os.environ["PATH"]
+
+# Probe sections
+SECTION_SYSTEM_CONFIG_NAME = "system_config"
+SECTION_SYSCALL_CONFIG_NAME = "syscall_config"
+SECTION_PROGRAM_TYPES_NAME = "program_types"
+SECTION_MAP_TYPES_NAME = "map_types"
+SECTION_HELPERS_NAME = "helpers"
+SECTION_MISC_NAME = "misc"
+SECTION_SYSTEM_CONFIG_PATTERN = b"Scanning system configuration..."
+SECTION_SYSCALL_CONFIG_PATTERN = b"Scanning system call availability..."
+SECTION_PROGRAM_TYPES_PATTERN = b"Scanning eBPF program types..."
+SECTION_MAP_TYPES_PATTERN = b"Scanning eBPF map types..."
+SECTION_HELPERS_PATTERN = b"Scanning eBPF helper functions..."
+SECTION_MISC_PATTERN = b"Scanning miscellaneous eBPF features..."
+
+
+class IfaceNotFoundError(Exception):
+ pass
+
+
+class UnprivilegedUserError(Exception):
+ pass
+
+
+def _bpftool(args, json=True):
+ _args = ["bpftool"]
+ if json:
+ _args.append("-j")
+ _args.extend(args)
+
+ res = subprocess.run(_args, capture_output=True)
+ return res.stdout
+
+
+def bpftool(args):
+ return _bpftool(args, json=False)
+
+
+def bpftool_json(args):
+ res = _bpftool(args)
+ return json.loads(res)
+
+
+def get_default_iface():
+ for iface in socket.if_nameindex():
+ if iface[1] != "lo":
+ return iface[1]
+ raise IfaceNotFoundError("Could not find any network interface to probe")
+
+
+def default_iface(f):
+ @functools.wraps(f)
+ def wrapper(*args, **kwargs):
+ iface = get_default_iface()
+ return f(*args, iface, **kwargs)
+ return wrapper
+
+
+class TestBpftool(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ if os.getuid() != 0:
+ raise UnprivilegedUserError("This test suite eeeds root privileges")
+
+ @default_iface
+ def test_feature_dev(self, iface):
+ expected_lines = [
+ SECTION_SYSCALL_CONFIG_PATTERN,
+ SECTION_PROGRAM_TYPES_PATTERN,
+ SECTION_MAP_TYPES_PATTERN,
+ SECTION_HELPERS_PATTERN,
+ SECTION_MISC_PATTERN,
+ ]
+
+ res = bpftool(["feature", "probe", "dev", iface])
+ for expected_line in expected_lines:
+ self.assertIn(expected_line, res)
+
+ @default_iface
+ def test_feature_dev_json(self, iface):
+ expected_keys = [
+ "syscall_config",
+ "program_types",
+ "map_types",
+ "helpers",
+ "misc",
+ ]
+
+ res = bpftool_json(["feature", "probe", "dev", iface])
+ self.assertCountEqual(res.keys(), expected_keys)
+
+ def test_feature_kernel(self):
+ expected_lines = [
+ SECTION_SYSTEM_CONFIG_PATTERN,
+ SECTION_SYSCALL_CONFIG_PATTERN,
+ SECTION_PROGRAM_TYPES_PATTERN,
+ SECTION_MAP_TYPES_PATTERN,
+ SECTION_HELPERS_PATTERN,
+ SECTION_MISC_PATTERN,
+ ]
+
+ res_default1 = bpftool(["feature"])
+ res_default2 = bpftool(["feature", "probe"])
+ res = bpftool(["feature", "probe", "kernel"])
+
+ for expected_line in expected_lines:
+ self.assertIn(expected_line, res_default1)
+ self.assertIn(expected_line, res_default2)
+ self.assertIn(expected_line, res)
+
+ def test_feature_kernel_json(self):
+ expected_keys = [
+ "system_config",
+ "syscall_config",
+ "program_types",
+ "map_types",
+ "helpers",
+ "misc",
+ ]
+
+ res_default1 = bpftool_json(["feature"])
+ self.assertCountEqual(res_default1.keys(), expected_keys)
+
+ res_default2 = bpftool_json(["feature", "probe"])
+ self.assertCountEqual(res_default2.keys(), expected_keys)
+
+ res = bpftool_json(["feature", "probe", "kernel"])
+ self.assertCountEqual(res.keys(), expected_keys)
+
+ def test_feature_section(self):
+ SectionTestCase = collections.namedtuple(
+ "SectionTestCase",
+ ["section_name", "expected_pattern", "unexpected_patterns"])
+ test_cases = [
+ SectionTestCase(
+ section_name=SECTION_SYSTEM_CONFIG_NAME,
+ expected_pattern=SECTION_SYSTEM_CONFIG_PATTERN,
+ unexpected_patterns=[SECTION_SYSCALL_CONFIG_PATTERN,
+ SECTION_PROGRAM_TYPES_PATTERN,
+ SECTION_MAP_TYPES_PATTERN,
+ SECTION_HELPERS_PATTERN,
+ SECTION_MISC_PATTERN]),
+ SectionTestCase(
+ section_name=SECTION_SYSCALL_CONFIG_NAME,
+ expected_pattern=SECTION_SYSCALL_CONFIG_PATTERN,
+ unexpected_patterns=[SECTION_SYSTEM_CONFIG_PATTERN,
+ SECTION_PROGRAM_TYPES_PATTERN,
+ SECTION_MAP_TYPES_PATTERN,
+ SECTION_HELPERS_PATTERN,
+ SECTION_MISC_PATTERN]),
+ SectionTestCase(
+ section_name=SECTION_PROGRAM_TYPES_NAME,
+ expected_pattern=SECTION_PROGRAM_TYPES_PATTERN,
+ unexpected_patterns=[SECTION_SYSTEM_CONFIG_PATTERN,
+ SECTION_SYSCALL_CONFIG_PATTERN,
+ SECTION_MAP_TYPES_PATTERN,
+ SECTION_HELPERS_PATTERN,
+ SECTION_MISC_PATTERN]),
+ SectionTestCase(
+ section_name=SECTION_MAP_TYPES_NAME,
+ expected_pattern=SECTION_MAP_TYPES_PATTERN,
+ unexpected_patterns=[SECTION_SYSTEM_CONFIG_PATTERN,
+ SECTION_SYSCALL_CONFIG_PATTERN,
+ SECTION_PROGRAM_TYPES_PATTERN,
+ SECTION_HELPERS_PATTERN,
+ SECTION_MISC_PATTERN]),
+ SectionTestCase(
+ section_name=SECTION_HELPERS_NAME,
+ expected_pattern=SECTION_HELPERS_PATTERN,
+ unexpected_patterns=[SECTION_SYSTEM_CONFIG_PATTERN,
+ SECTION_SYSCALL_CONFIG_PATTERN,
+ SECTION_PROGRAM_TYPES_PATTERN,
+ SECTION_MAP_TYPES_PATTERN,
+ SECTION_MISC_PATTERN]),
+ SectionTestCase(
+ section_name=SECTION_MISC_NAME,
+ expected_pattern=SECTION_MISC_PATTERN,
+ unexpected_patterns=[SECTION_SYSTEM_CONFIG_PATTERN,
+ SECTION_SYSCALL_CONFIG_PATTERN,
+ SECTION_PROGRAM_TYPES_PATTERN,
+ SECTION_MAP_TYPES_PATTERN,
+ SECTION_HELPERS_PATTERN]),
+ ]
+
+ for tc in test_cases:
+ res = bpftool(["feature", "probe", "kernel",
+ "section", tc.section_name])
+ self.assertIn(tc.expected_pattern, res)
+ for pattern in tc.unexpected_patterns:
+ self.assertNotIn(pattern, res)
+
+ def test_feature_section_json(self):
+ res_syscall_config = bpftool_json(["feature", "probe", "kernel",
+ "section", "syscall_config"])
+ self.assertCountEqual(res_syscall_config.keys(), ["syscall_config"])
+
+ res_system_config = bpftool_json(["feature", "probe", "kernel",
+ "section", "system_config"])
+ self.assertCountEqual(res_system_config.keys(), ["system_config"])
+
+ res_program_types = bpftool_json(["feature", "probe", "kernel",
+ "section", "program_types"])
+ self.assertCountEqual(res_program_types.keys(), ["program_types"])
+
+ res_map_types = bpftool_json(["feature", "probe", "kernel",
+ "section", "map_types"])
+ self.assertCountEqual(res_map_types.keys(), ["map_types"])
+
+ res_helpers = bpftool_json(["feature", "probe", "kernel",
+ "section", "helpers"])
+ self.assertCountEqual(res_helpers.keys(), ["helpers"])
+
+ res_misc = bpftool_json(["feature", "probe", "kernel",
+ "section", "misc"])
+ self.assertCountEqual(res_misc.keys(), ["misc"])
+
+ def _assert_pattern_in_dict(self, dct, pattern, check_keys=False):
+ """Check if all string values inside dictionary contain the given
+ pattern.
+ """
+ for key, value in dct.items():
+ if check_keys:
+ self.assertIn(pattern, key)
+ if isinstance(value, dict):
+ self._assert_pattern_in_dict(value, pattern, check_keys=True)
+ elif isinstance(value, str):
+ self.assertIn(pattern, value)
+
+ def _assert_pattern_not_in_dict(self, dct, pattern, check_keys=False):
+ """Check if all string values inside dictionary do not containe the
+ given pattern.
+ """
+ for key, value in dct.items():
+ if check_keys:
+ self.assertNotIn(pattern, key)
+ if isinstance(value, dict):
+ self._assert_pattern_not_in_dict(value, pattern,
+ check_keys=True)
+ elif isinstance(value, str):
+ self.assertNotIn(pattern, value)
+
+ def test_feature_filter_in_json(self):
+ res = bpftool_json(["feature", "probe", "kernel",
+ "filter_in", "trace"])
+ self._assert_pattern_in_dict(res, "trace")
+
+ def test_feature_filter_out_json(self):
+ res = bpftool_json(["feature", "probe", "kernel",
+ "filter_out", "trace"])
+ self._assert_pattern_not_in_dict(res, "trace")
+
+ def test_feature_macros(self):
+ expected_patterns = [
+ b"/\*\*\* System call availability \*\*\*/",
+ b"#define HAVE_BPF_SYSCALL",
+ b"/\*\*\* eBPF program types \*\*\*/",
+ b"#define HAVE.*PROG_TYPE",
+ b"/\*\*\* eBPF map types \*\*\*/",
+ b"#define HAVE.*MAP_TYPE",
+ b"/\*\*\* eBPF helper functions \*\*\*/",
+ b"#define HAVE.*HELPER",
+ b"/\*\*\* eBPF misc features \*\*\*/",
+ ]
+
+ res = bpftool(["feature", "probe", "macros"])
+ for pattern in expected_patterns:
+ self.assertRegex(res, pattern)
diff --git a/tools/testing/selftests/bpf/test_bpftool.sh b/tools/testing/selftests/bpf/test_bpftool.sh
new file mode 100755
index 000000000000..66690778e36d
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_bpftool.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2020 SUSE LLC.
+
+python3 -m unittest -v test_bpftool.TestBpftool
--
2.25.0

2020-02-18 19:04:30

by Michal Rostecki

[permalink] [raw]
Subject: [PATCH bpf-next 2/6] bpftool: Allow to select a specific section to probe

This change introduces a new positional argument "section" which takes
the following arguments:

- system_config
- syscall_config
- program_types
- map_types
- helpers
- misc

If "section" argument is defined, only that particular section is going
to be probed and printed. The only section which is always going to be
probed is "syscall_config", but if the other section was provided as an
argument, "syscall_config" check will perform silently without printing
and exit bpftool if the bpf() syscall is not available (because in that
case running any probe has no sense).

Signed-off-by: Michal Rostecki <[email protected]>
---
tools/bpf/bpftool/feature.c | 159 ++++++++++++++++++++++++++++--------
1 file changed, 123 insertions(+), 36 deletions(-)

diff --git a/tools/bpf/bpftool/feature.c b/tools/bpf/bpftool/feature.c
index 345e4a2b4f53..cfba0faf148f 100644
--- a/tools/bpf/bpftool/feature.c
+++ b/tools/bpf/bpftool/feature.c
@@ -22,6 +22,13 @@
# define PROC_SUPER_MAGIC 0x9fa0
#endif

+#define SECTION_SYSCALL_CONFIG "syscall_config"
+#define SECTION_SYSTEM_CONFIG "system_config"
+#define SECTION_PROGRAM_TYPES "program_types"
+#define SECTION_MAP_TYPES "map_types"
+#define SECTION_HELPERS "helpers"
+#define SECTION_MISC "misc"
+
enum probe_component {
COMPONENT_UNSPEC,
COMPONENT_KERNEL,
@@ -436,24 +443,26 @@ static void probe_kernel_image_config(void)
}
}

-static bool probe_bpf_syscall(const char *define_prefix)
+static bool
+probe_bpf_syscall(bool print_syscall_config, const char *define_prefix)
{
bool res;

bpf_load_program(BPF_PROG_TYPE_UNSPEC, NULL, 0, NULL, 0, NULL, 0);
res = (errno != ENOSYS);

- print_bool_feature("have_bpf_syscall",
- "bpf() syscall",
- "BPF_SYSCALL",
- res, define_prefix);
+ if (print_syscall_config)
+ print_bool_feature("have_bpf_syscall",
+ "bpf() syscall",
+ "BPF_SYSCALL",
+ res, define_prefix);

return res;
}

static void
-probe_prog_type(enum bpf_prog_type prog_type, bool *supported_types,
- const char *define_prefix, __u32 ifindex)
+probe_prog_type(bool print_program_types, enum bpf_prog_type prog_type,
+ bool *supported_types, const char *define_prefix, __u32 ifindex)
{
char feat_name[128], plain_desc[128], define_name[128];
const char *plain_comment = "eBPF program_type ";
@@ -484,8 +493,10 @@ probe_prog_type(enum bpf_prog_type prog_type, bool *supported_types,
sprintf(define_name, "%s_prog_type", prog_type_name[prog_type]);
uppercase(define_name, sizeof(define_name));
sprintf(plain_desc, "%s%s", plain_comment, prog_type_name[prog_type]);
- print_bool_feature(feat_name, plain_desc, define_name, res,
- define_prefix);
+
+ if (print_program_types)
+ print_bool_feature(feat_name, plain_desc, define_name, res,
+ define_prefix);
}

static void
@@ -587,7 +598,7 @@ section_system_config(enum probe_component target, const char *define_prefix)
if (define_prefix)
break;

- print_start_section("system_config",
+ print_start_section(SECTION_SYSTEM_CONFIG,
"Scanning system configuration...",
NULL, /* define_comment never used here */
NULL); /* define_prefix always NULL here */
@@ -608,42 +619,48 @@ section_system_config(enum probe_component target, const char *define_prefix)
}
}

-static bool section_syscall_config(const char *define_prefix)
+static bool
+section_syscall_config(bool print_syscall_config, const char *define_prefix)
{
bool res;

- print_start_section("syscall_config",
- "Scanning system call availability...",
- "/*** System call availability ***/",
- define_prefix);
- res = probe_bpf_syscall(define_prefix);
- print_end_section();
+ if (print_syscall_config)
+ print_start_section(SECTION_SYSCALL_CONFIG,
+ "Scanning system call availability...",
+ "/*** System call availability ***/",
+ define_prefix);
+ res = probe_bpf_syscall(print_syscall_config, define_prefix);
+ if (print_syscall_config)
+ print_end_section();

return res;
}

static void
-section_program_types(bool *supported_types, const char *define_prefix,
- __u32 ifindex)
+section_program_types(bool print_program_types, bool *supported_types,
+ const char *define_prefix, __u32 ifindex)
{
unsigned int i;

- print_start_section("program_types",
- "Scanning eBPF program types...",
- "/*** eBPF program types ***/",
- define_prefix);
+ if (print_program_types)
+ print_start_section(SECTION_PROGRAM_TYPES,
+ "Scanning eBPF program types...",
+ "/*** eBPF program types ***/",
+ define_prefix);

for (i = BPF_PROG_TYPE_UNSPEC + 1; i < ARRAY_SIZE(prog_type_name); i++)
- probe_prog_type(i, supported_types, define_prefix, ifindex);
+ probe_prog_type(print_program_types, i, supported_types,
+ define_prefix, ifindex);

- print_end_section();
+ if (print_program_types)
+ print_end_section();
}

static void section_map_types(const char *define_prefix, __u32 ifindex)
{
unsigned int i;

- print_start_section("map_types",
+ print_start_section(SECTION_MAP_TYPES,
"Scanning eBPF map types...",
"/*** eBPF map types ***/",
define_prefix);
@@ -659,7 +676,7 @@ section_helpers(bool *supported_types, const char *define_prefix, __u32 ifindex)
{
unsigned int i;

- print_start_section("helpers",
+ print_start_section(SECTION_HELPERS,
"Scanning eBPF helper functions...",
"/*** eBPF helper functions ***/",
define_prefix);
@@ -688,7 +705,7 @@ section_helpers(bool *supported_types, const char *define_prefix, __u32 ifindex)

static void section_misc(const char *define_prefix, __u32 ifindex)
{
- print_start_section("misc",
+ print_start_section(SECTION_MISC,
"Scanning miscellaneous eBPF features...",
"/*** eBPF misc features ***/",
define_prefix);
@@ -699,8 +716,25 @@ static void section_misc(const char *define_prefix, __u32 ifindex)
static int do_probe(int argc, char **argv)
{
enum probe_component target = COMPONENT_UNSPEC;
+ /* Syscall probe is always performed, because performing any other
+ * checks without bpf() syscall does not make sense and the program
+ * should exit.
+ */
+ bool print_syscall_config = false;
const char *define_prefix = NULL;
+ bool check_system_config = false;
+ /* Program types probes are needed if helper probes are going to be
+ * performed. Therefore we should differentiate between checking and
+ * printing supported program types. If only helper checks were
+ * requested, program types probes will be performed, but not printed.
+ */
+ bool check_program_types = false;
+ bool print_program_types = false;
bool supported_types[128] = {};
+ bool check_map_types = false;
+ bool check_helpers = false;
+ bool check_section = false;
+ bool check_misc = false;
__u32 ifindex = 0;
char *ifname;

@@ -740,6 +774,39 @@ static int do_probe(int argc, char **argv)
strerror(errno));
return -1;
}
+ } else if (is_prefix(*argv, "section")) {
+ check_section = true;
+ NEXT_ARG();
+ if (is_prefix(*argv, SECTION_SYSTEM_CONFIG)) {
+ check_system_config = true;
+ } else if (is_prefix(*argv, SECTION_SYSCALL_CONFIG)) {
+ print_syscall_config = true;
+ } else if (is_prefix(*argv, SECTION_PROGRAM_TYPES)) {
+ check_program_types = true;
+ print_program_types = true;
+ } else if (is_prefix(*argv, SECTION_MAP_TYPES)) {
+ check_map_types = true;
+ } else if (is_prefix(*argv, SECTION_HELPERS)) {
+ /* When helpers probes are requested, program
+ * types probes have to be performed, but they
+ * may not be printed.
+ */
+ check_program_types = true;
+ check_helpers = true;
+ } else if (is_prefix(*argv, SECTION_MISC)) {
+ check_misc = true;
+ } else {
+ p_err("unrecognized section '%s', available sections: %s, %s, %s, %s, %s, %s",
+ *argv,
+ SECTION_SYSTEM_CONFIG,
+ SECTION_SYSCALL_CONFIG,
+ SECTION_PROGRAM_TYPES,
+ SECTION_MAP_TYPES,
+ SECTION_HELPERS,
+ SECTION_MISC);
+ return -1;
+ }
+ NEXT_ARG();
} else if (is_prefix(*argv, "macros") && !define_prefix) {
define_prefix = "";
NEXT_ARG();
@@ -764,19 +831,36 @@ static int do_probe(int argc, char **argv)
}
}

+ /* Perform all checks if specific section check was not requested. */
+ if (!check_section) {
+ print_syscall_config = true;
+ check_system_config = true;
+ check_program_types = true;
+ print_program_types = true;
+ check_map_types = true;
+ check_helpers = true;
+ check_misc = true;
+ }
+
if (json_output) {
define_prefix = NULL;
jsonw_start_object(json_wtr);
}

- section_system_config(target, define_prefix);
- if (!section_syscall_config(define_prefix))
+ if (check_system_config)
+ section_system_config(target, define_prefix);
+ if (!section_syscall_config(print_syscall_config, define_prefix))
/* bpf() syscall unavailable, don't probe other BPF features */
goto exit_close_json;
- section_program_types(supported_types, define_prefix, ifindex);
- section_map_types(define_prefix, ifindex);
- section_helpers(supported_types, define_prefix, ifindex);
- section_misc(define_prefix, ifindex);
+ if (check_program_types)
+ section_program_types(print_program_types, supported_types,
+ define_prefix, ifindex);
+ if (check_map_types)
+ section_map_types(define_prefix, ifindex);
+ if (check_helpers)
+ section_helpers(supported_types, define_prefix, ifindex);
+ if (check_misc)
+ section_misc(define_prefix, ifindex);

exit_close_json:
if (json_output)
@@ -794,12 +878,15 @@ static int do_help(int argc, char **argv)
}

fprintf(stderr,
- "Usage: %s %s probe [COMPONENT] [macros [prefix PREFIX]]\n"
+ "Usage: %s %s probe [COMPONENT] [section SECTION] [macros [prefix PREFIX]]\n"
" %s %s help\n"
"\n"
" COMPONENT := { kernel | dev NAME }\n"
+ " SECTION := { %s | %s | %s | %s | %s | %s }\n"
"",
- bin_name, argv[-2], bin_name, argv[-2]);
+ bin_name, argv[-2], bin_name, argv[-2], SECTION_SYSTEM_CONFIG,
+ SECTION_SYSCALL_CONFIG, SECTION_PROGRAM_TYPES,
+ SECTION_MAP_TYPES, SECTION_HELPERS, SECTION_MISC);

return 0;
}
--
2.25.0

2020-02-19 03:03:26

by Alexei Starovoitov

[permalink] [raw]
Subject: Re: [PATCH bpf-next 0/6] bpftool: Allow to select sections and filter probes

On Tue, Feb 18, 2020 at 11:02 AM Michal Rostecki <[email protected]> wrote:
>
> This patch series extend the "bpftool feature" subcommand with the
> new positional arguments:
>
> - "section", which allows to select a specific section of probes (i.e.
> "system_config", "program_types", "map_types");
> - "filter_in", which allows to select only probes which matches the
> given regex pattern;
> - "filter_out", which allows to filter out probes which do not match the
> given regex pattern.
>
> The main motivation behind those changes is ability the fact that some
> probes (for example those related to "trace" or "write_user" helpers)
> emit dmesg messages which might be confusing for people who are running
> on production environments. For details see the Cilium issue[0].
>
> [0] https://github.com/cilium/cilium/issues/10048

The motivation is clear, but I think the users shouldn't be made
aware of such implementation details. I think instead of filter_in/out
it's better to do 'full or safe' mode of probing.
By default it can do all the probing that doesn't cause
extra dmesgs and in 'full' mode it can probe everything.

2020-02-19 12:33:24

by Michal Rostecki

[permalink] [raw]
Subject: Re: [PATCH bpf-next 0/6] bpftool: Allow to select sections and filter probes

On 2/19/20 4:02 AM, Alexei Starovoitov wrote:
> The motivation is clear, but I think the users shouldn't be made
> aware of such implementation details. I think instead of filter_in/out
> it's better to do 'full or safe' mode of probing.
> By default it can do all the probing that doesn't cause
> extra dmesgs and in 'full' mode it can probe everything.

Alright, then I will send later v2 where the "internal" implementation
(filtering out based on regex) stays similar (filter_out will stay in
the code without being exposed to users, filter_in will be removed). And
the exposed option of "safe" probing will just apply the
"(trace|write_user)" filter_out pattern. Does it sound good?

2020-02-19 16:38:20

by Alexei Starovoitov

[permalink] [raw]
Subject: Re: [PATCH bpf-next 0/6] bpftool: Allow to select sections and filter probes

On Wed, Feb 19, 2020 at 4:33 AM Michal Rostecki <[email protected]> wrote:
>
> On 2/19/20 4:02 AM, Alexei Starovoitov wrote:
> > The motivation is clear, but I think the users shouldn't be made
> > aware of such implementation details. I think instead of filter_in/out
> > it's better to do 'full or safe' mode of probing.
> > By default it can do all the probing that doesn't cause
> > extra dmesgs and in 'full' mode it can probe everything.
>
> Alright, then I will send later v2 where the "internal" implementation
> (filtering out based on regex) stays similar (filter_out will stay in
> the code without being exposed to users, filter_in will be removed). And
> the exposed option of "safe" probing will just apply the
> "(trace|write_user)" filter_out pattern. Does it sound good?

yes. If implementation is doing filter_in and applying 'trace_printk|write_user'
strings hidden within bpftool than I think it should be good.
What do you think the default should be?
It feels to me that the default should not be causing dmesg prints.
So only addition flag for bpftool command line will be 'bpftool
feature probe full'

2020-02-19 16:52:59

by Daniel Borkmann

[permalink] [raw]
Subject: Re: [PATCH bpf-next 0/6] bpftool: Allow to select sections and filter probes

On 2/19/20 5:37 PM, Alexei Starovoitov wrote:
> On Wed, Feb 19, 2020 at 4:33 AM Michal Rostecki <[email protected]> wrote:
>>
>> On 2/19/20 4:02 AM, Alexei Starovoitov wrote:
>>> The motivation is clear, but I think the users shouldn't be made
>>> aware of such implementation details. I think instead of filter_in/out
>>> it's better to do 'full or safe' mode of probing.
>>> By default it can do all the probing that doesn't cause
>>> extra dmesgs and in 'full' mode it can probe everything.
>>
>> Alright, then I will send later v2 where the "internal" implementation
>> (filtering out based on regex) stays similar (filter_out will stay in
>> the code without being exposed to users, filter_in will be removed). And
>> the exposed option of "safe" probing will just apply the
>> "(trace|write_user)" filter_out pattern. Does it sound good?
>
> yes. If implementation is doing filter_in and applying 'trace_printk|write_user'
> strings hidden within bpftool than I think it should be good.
> What do you think the default should be?
> It feels to me that the default should not be causing dmesg prints.
> So only addition flag for bpftool command line will be 'bpftool
> feature probe full'

Agree, that makes sense to me.

2020-02-19 20:57:34

by Michal Rostecki

[permalink] [raw]
Subject: Re: [PATCH bpf-next 0/6] bpftool: Allow to select sections and filter probes

On 2/19/20 5:52 PM, Daniel Borkmann wrote:
> On 2/19/20 5:37 PM, Alexei Starovoitov wrote:
>> On Wed, Feb 19, 2020 at 4:33 AM Michal Rostecki
>> <[email protected]> wrote:
>>>
>>> On 2/19/20 4:02 AM, Alexei Starovoitov wrote:
>>>> The motivation is clear, but I think the users shouldn't be made
>>>> aware of such implementation details. I think instead of filter_in/out
>>>> it's better to do 'full or safe' mode of probing.
>>>> By default it can do all the probing that doesn't cause
>>>> extra dmesgs and in 'full' mode it can probe everything.
>>>
>>> Alright, then I will send later v2 where the "internal" implementation
>>> (filtering out based on regex) stays similar (filter_out will stay in
>>> the code without being exposed to users, filter_in will be removed). And
>>> the exposed option of "safe" probing will just apply the
>>> "(trace|write_user)" filter_out pattern. Does it sound good?
>>
>> yes. If implementation is doing filter_in and applying
>> 'trace_printk|write_user'
>> strings hidden within bpftool than I think it should be good.
>> What do you think the default should be?
>> It feels to me that the default should not be causing dmesg prints.
>> So only addition flag for bpftool command line will be 'bpftool
>> feature probe full'
>
> Agree, that makes sense to me.

Makes sense to me as well. I will do that in v2.