Commit 4bc98846 authored by Daniel Borkmann's avatar Daniel Borkmann

Merge branch 'bpf-bpftool-probes'

Michal Rostecki says:

====================
Feature probes in bpftool related to bpf_probe_write_user and
bpf_trace_printk helpers emit dmesg warnings which might be confusing
for people running bpftool on production environments. This patch series
addresses that by filtering them out by default and introducing the new
positional argument "full" which enables all available probes.

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].

v1 -> v2:
- Do not expose regex filters to users, keep filtering logic internal,
expose only the "full" option for including probes which emit dmesg
warnings.

v2 -> v3:
- Do not use regex for filtering out probes, use function IDs directly.
- Fix bash completion - in v2 only "prefix" was proposed after "macros",
  "dev" and "kernel" were not.
- Rephrase the man page paragraph, highlight helper function names.
- Remove tests which parse the plain output of bpftool (except the
  header/macros test), focus on testing JSON output instead.
- Add test which compares the output with and without "full" option.

v3 -> v4:
- Use enum to check for helper functions.
- Make selftests compatible with older versions of Python 3.x than 3.7.

  [0] https://github.com/cilium/cilium/issues/10048
====================
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
parents 3494bec0 73633274
...@@ -19,19 +19,24 @@ SYNOPSIS ...@@ -19,19 +19,24 @@ SYNOPSIS
FEATURE COMMANDS FEATURE COMMANDS
================ ================
| **bpftool** **feature probe** [*COMPONENT*] [**macros** [**prefix** *PREFIX*]] | **bpftool** **feature probe** [*COMPONENT*] [**full**] [**macros** [**prefix** *PREFIX*]]
| **bpftool** **feature help** | **bpftool** **feature help**
| |
| *COMPONENT* := { **kernel** | **dev** *NAME* } | *COMPONENT* := { **kernel** | **dev** *NAME* }
DESCRIPTION DESCRIPTION
=========== ===========
**bpftool feature probe** [**kernel**] [**macros** [**prefix** *PREFIX*]] **bpftool feature probe** [**kernel**] [**full**] [**macros** [**prefix** *PREFIX*]]
Probe the running kernel and dump a number of eBPF-related Probe the running kernel and dump a number of eBPF-related
parameters, such as availability of the **bpf()** system call, parameters, such as availability of the **bpf()** system call,
JIT status, eBPF program types availability, eBPF helper JIT status, eBPF program types availability, eBPF helper
functions availability, and more. functions availability, and more.
By default, bpftool **does not run probes** for
**bpf_probe_write_user**\ () and **bpf_trace_printk**\()
helpers which print warnings to kernel logs. To enable them
and run all probes, the **full** keyword should be used.
If the **macros** keyword (but not the **-j** option) is If the **macros** keyword (but not the **-j** option) is
passed, a subset of the output is dumped as a list of passed, a subset of the output is dumped as a list of
**#define** macros that are ready to be included in a C **#define** macros that are ready to be included in a C
...@@ -44,16 +49,12 @@ DESCRIPTION ...@@ -44,16 +49,12 @@ DESCRIPTION
Keyword **kernel** can be omitted. If no probe target is Keyword **kernel** can be omitted. If no probe target is
specified, probing the kernel is the default behaviour. specified, probing the kernel is the default behaviour.
Note that when probed, some eBPF helpers (e.g. **bpftool feature probe dev** *NAME* [**full**] [**macros** [**prefix** *PREFIX*]]
**bpf_trace_printk**\ () or **bpf_probe_write_user**\ ()) may
print warnings to kernel logs.
**bpftool feature probe dev** *NAME* [**macros** [**prefix** *PREFIX*]]
Probe network device for supported eBPF features and dump Probe network device for supported eBPF features and dump
results to the console. results to the console.
The two keywords **macros** and **prefix** have the same The keywords **full**, **macros** and **prefix** have the
role as when probing the kernel. same role as when probing the kernel.
**bpftool feature help** **bpftool feature help**
Print short help message. Print short help message.
......
...@@ -984,11 +984,12 @@ _bpftool() ...@@ -984,11 +984,12 @@ _bpftool()
probe) probe)
[[ $prev == "prefix" ]] && return 0 [[ $prev == "prefix" ]] && return 0
if _bpftool_search_list 'macros'; then if _bpftool_search_list 'macros'; then
COMPREPLY+=( $( compgen -W 'prefix' -- "$cur" ) ) _bpftool_once_attr 'prefix'
else else
COMPREPLY+=( $( compgen -W 'macros' -- "$cur" ) ) COMPREPLY+=( $( compgen -W 'macros' -- "$cur" ) )
fi fi
_bpftool_one_of_list 'kernel dev' _bpftool_one_of_list 'kernel dev'
_bpftool_once_attr 'full'
return 0 return 0
;; ;;
*) *)
......
...@@ -112,18 +112,12 @@ print_start_section(const char *json_title, const char *plain_title, ...@@ -112,18 +112,12 @@ print_start_section(const char *json_title, const char *plain_title,
} }
} }
static void static void print_end_section(void)
print_end_then_start_section(const char *json_title, const char *plain_title,
const char *define_comment,
const char *define_prefix)
{ {
if (json_output) if (json_output)
jsonw_end_object(json_wtr); jsonw_end_object(json_wtr);
else else
printf("\n"); printf("\n");
print_start_section(json_title, plain_title, define_comment,
define_prefix);
} }
/* Probing functions */ /* Probing functions */
...@@ -519,14 +513,39 @@ probe_map_type(enum bpf_map_type map_type, const char *define_prefix, ...@@ -519,14 +513,39 @@ probe_map_type(enum bpf_map_type map_type, const char *define_prefix,
define_prefix); define_prefix);
} }
static void
probe_helper_for_progtype(enum bpf_prog_type prog_type, bool supported_type,
const char *define_prefix, unsigned int id,
const char *ptype_name, __u32 ifindex)
{
bool res;
if (!supported_type)
res = false;
else
res = bpf_probe_helper(id, prog_type, ifindex);
if (json_output) {
if (res)
jsonw_string(json_wtr, helper_name[id]);
} else if (define_prefix) {
printf("#define %sBPF__PROG_TYPE_%s__HELPER_%s %s\n",
define_prefix, ptype_name, helper_name[id],
res ? "1" : "0");
} else {
if (res)
printf("\n\t- %s", helper_name[id]);
}
}
static void static void
probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type, probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type,
const char *define_prefix, __u32 ifindex) const char *define_prefix, bool full_mode,
__u32 ifindex)
{ {
const char *ptype_name = prog_type_name[prog_type]; const char *ptype_name = prog_type_name[prog_type];
char feat_name[128]; char feat_name[128];
unsigned int id; unsigned int id;
bool res;
if (ifindex) if (ifindex)
/* Only test helpers for offload-able program types */ /* Only test helpers for offload-able program types */
...@@ -548,21 +567,19 @@ probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type, ...@@ -548,21 +567,19 @@ probe_helpers_for_progtype(enum bpf_prog_type prog_type, bool supported_type,
} }
for (id = 1; id < ARRAY_SIZE(helper_name); id++) { for (id = 1; id < ARRAY_SIZE(helper_name); id++) {
if (!supported_type) /* Skip helper functions which emit dmesg messages when not in
res = false; * the full mode.
else */
res = bpf_probe_helper(id, prog_type, ifindex); switch (id) {
case BPF_FUNC_trace_printk:
if (json_output) { case BPF_FUNC_probe_write_user:
if (res) if (!full_mode)
jsonw_string(json_wtr, helper_name[id]); continue;
} else if (define_prefix) { /* fallthrough */
printf("#define %sBPF__PROG_TYPE_%s__HELPER_%s %s\n", default:
define_prefix, ptype_name, helper_name[id], probe_helper_for_progtype(prog_type, supported_type,
res ? "1" : "0"); define_prefix, id, ptype_name,
} else { ifindex);
if (res)
printf("\n\t- %s", helper_name[id]);
} }
} }
...@@ -584,13 +601,132 @@ probe_large_insn_limit(const char *define_prefix, __u32 ifindex) ...@@ -584,13 +601,132 @@ probe_large_insn_limit(const char *define_prefix, __u32 ifindex)
res, define_prefix); 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,
bool full_mode, __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, full_mode, 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) static int do_probe(int argc, char **argv)
{ {
enum probe_component target = COMPONENT_UNSPEC; enum probe_component target = COMPONENT_UNSPEC;
const char *define_prefix = NULL; const char *define_prefix = NULL;
bool supported_types[128] = {}; bool supported_types[128] = {};
bool full_mode = false;
__u32 ifindex = 0; __u32 ifindex = 0;
unsigned int i;
char *ifname; char *ifname;
/* Detection assumes user has sufficient privileges (CAP_SYS_ADMIN). /* Detection assumes user has sufficient privileges (CAP_SYS_ADMIN).
...@@ -629,6 +765,9 @@ static int do_probe(int argc, char **argv) ...@@ -629,6 +765,9 @@ static int do_probe(int argc, char **argv)
strerror(errno)); strerror(errno));
return -1; return -1;
} }
} else if (is_prefix(*argv, "full")) {
full_mode = true;
NEXT_ARG();
} else if (is_prefix(*argv, "macros") && !define_prefix) { } else if (is_prefix(*argv, "macros") && !define_prefix) {
define_prefix = ""; define_prefix = "";
NEXT_ARG(); NEXT_ARG();
...@@ -658,97 +797,19 @@ static int do_probe(int argc, char **argv) ...@@ -658,97 +797,19 @@ static int do_probe(int argc, char **argv)
jsonw_start_object(json_wtr); jsonw_start_object(json_wtr);
} }
switch (target) { section_system_config(target, define_prefix);
case COMPONENT_KERNEL: if (!section_syscall_config(define_prefix))
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))
/* bpf() syscall unavailable, don't probe other BPF features */ /* bpf() syscall unavailable, don't probe other BPF features */
goto exit_close_json; goto exit_close_json;
section_program_types(supported_types, define_prefix, ifindex);
print_end_then_start_section("program_types", section_map_types(define_prefix, ifindex);
"Scanning eBPF program types...", section_helpers(supported_types, define_prefix, full_mode, ifindex);
"/*** eBPF program types ***/", section_misc(define_prefix, ifindex);
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);
exit_close_json: exit_close_json:
if (json_output) { if (json_output)
/* End current "section" of probes */
jsonw_end_object(json_wtr);
/* End root object */ /* End root object */
jsonw_end_object(json_wtr); jsonw_end_object(json_wtr);
}
return 0; return 0;
} }
...@@ -761,7 +822,7 @@ static int do_help(int argc, char **argv) ...@@ -761,7 +822,7 @@ static int do_help(int argc, char **argv)
} }
fprintf(stderr, fprintf(stderr,
"Usage: %s %s probe [COMPONENT] [macros [prefix PREFIX]]\n" "Usage: %s %s probe [COMPONENT] [full] [macros [prefix PREFIX]]\n"
" %s %s help\n" " %s %s help\n"
"\n" "\n"
" COMPONENT := { kernel | dev NAME }\n" " COMPONENT := { kernel | dev NAME }\n"
......
...@@ -3,4 +3,7 @@ gpiogpio-hammer ...@@ -3,4 +3,7 @@ gpiogpio-hammer
gpioinclude/ gpioinclude/
gpiolsgpio gpiolsgpio
tpm2/SpaceTest.log tpm2/SpaceTest.log
tpm2/*.pyc
# Python bytecode and cache
__pycache__/
*.py[cod]
...@@ -62,7 +62,8 @@ TEST_PROGS := test_kmod.sh \ ...@@ -62,7 +62,8 @@ TEST_PROGS := test_kmod.sh \
test_tc_tunnel.sh \ test_tc_tunnel.sh \
test_tc_edt.sh \ test_tc_edt.sh \
test_xdping.sh \ test_xdping.sh \
test_bpftool_build.sh test_bpftool_build.sh \
test_bpftool.sh
TEST_PROGS_EXTENDED := with_addr.sh \ TEST_PROGS_EXTENDED := with_addr.sh \
with_tunnels.sh \ with_tunnels.sh \
......
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2020 SUSE LLC.
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"]
class IfaceNotFoundError(Exception):
pass
class UnprivilegedUserError(Exception):
pass
def _bpftool(args, json=True):
_args = ["bpftool"]
if json:
_args.append("-j")
_args.extend(args)
return subprocess.check_output(_args)
def bpftool(args):
return _bpftool(args, json=False).decode("utf-8")
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 needs root privileges")
@default_iface
def test_feature_dev_json(self, iface):
unexpected_helpers = [
"bpf_probe_write_user",
"bpf_trace_printk",
]
expected_keys = [
"syscall_config",
"program_types",
"map_types",
"helpers",
"misc",
]
res = bpftool_json(["feature", "probe", "dev", iface])
# Check if the result has all expected keys.
self.assertCountEqual(res.keys(), expected_keys)
# Check if unexpected helpers are not included in helpers probes
# result.
for helpers in res["helpers"].values():
for unexpected_helper in unexpected_helpers:
self.assertNotIn(unexpected_helper, helpers)
def test_feature_kernel(self):
test_cases = [
bpftool_json(["feature", "probe", "kernel"]),
bpftool_json(["feature", "probe"]),
bpftool_json(["feature"]),
]
unexpected_helpers = [
"bpf_probe_write_user",
"bpf_trace_printk",
]
expected_keys = [
"syscall_config",
"system_config",
"program_types",
"map_types",
"helpers",
"misc",
]
for tc in test_cases:
# Check if the result has all expected keys.
self.assertCountEqual(tc.keys(), expected_keys)
# Check if unexpected helpers are not included in helpers probes
# result.
for helpers in tc["helpers"].values():
for unexpected_helper in unexpected_helpers:
self.assertNotIn(unexpected_helper, helpers)
def test_feature_kernel_full(self):
test_cases = [
bpftool_json(["feature", "probe", "kernel", "full"]),
bpftool_json(["feature", "probe", "full"]),
]
expected_helpers = [
"bpf_probe_write_user",
"bpf_trace_printk",
]
for tc in test_cases:
# Check if expected helpers are included at least once in any
# helpers list for any program type. Unfortunately we cannot assume
# that they will be included in all program types or a specific
# subset of programs. It depends on the kernel version and
# configuration.
found_helpers = False
for helpers in tc["helpers"].values():
if all(expected_helper in helpers
for expected_helper in expected_helpers):
found_helpers = True
break
self.assertTrue(found_helpers)
def test_feature_kernel_full_vs_not_full(self):
full_res = bpftool_json(["feature", "probe", "full"])
not_full_res = bpftool_json(["feature", "probe"])
not_full_set = set()
full_set = set()
for helpers in full_res["helpers"].values():
for helper in helpers:
full_set.add(helper)
for helpers in not_full_res["helpers"].values():
for helper in helpers:
not_full_set.add(helper)
self.assertCountEqual(full_set - not_full_set,
{"bpf_probe_write_user", "bpf_trace_printk"})
self.assertCountEqual(not_full_set - full_set, set())
def test_feature_macros(self):
expected_patterns = [
r"/\*\*\* System call availability \*\*\*/",
r"#define HAVE_BPF_SYSCALL",
r"/\*\*\* eBPF program types \*\*\*/",
r"#define HAVE.*PROG_TYPE",
r"/\*\*\* eBPF map types \*\*\*/",
r"#define HAVE.*MAP_TYPE",
r"/\*\*\* eBPF helper functions \*\*\*/",
r"#define HAVE.*HELPER",
r"/\*\*\* eBPF misc features \*\*\*/",
]
res = bpftool(["feature", "probe", "macros"])
for pattern in expected_patterns:
self.assertRegex(res, pattern)
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2020 SUSE LLC.
python3 -m unittest -v test_bpftool.TestBpftool
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment