diff mbox series

[bpf-next,7/8] tools: bpftool: add probes for a network device

Message ID 20181213121922.6652-8-quentin.monnet@netronome.com
State Changes Requested, archived
Delegated to: BPF Maintainers
Headers show
Series tools: bpftool: add probes for system and device | expand

Commit Message

Quentin Monnet Dec. 13, 2018, 12:19 p.m. UTC
bpftool gained support for probing the current system in order to see
what program and map types, and what helpers are available on that
system. This patch adds the possibility to pass an interface index to
the kernel when trying to load the programs or to create the maps, in
order to see what items a given network device can support.

A new keyword "dev <ifname>" can be used as an alternative to "kernel"
to indicate that the given device should be tested. If no target ("dev"
or "kernel") is specified bpftool defaults to probing the kernel.

Sample output:

    # bpftool -p feature probe dev lo
    {
        "syscall_config": {
            "kernel_version_code": 267008,
            "have_bpf_syscall": true
        },
        "program_types": {
            "have_sched_cls_prog_type": false,
            "have_xdp_prog_type": false
        },
        ...
    }

As the target is a network device, /proc/ parameters and kernel
configuration are NOT dumped. Availability of the bpf() syscall and
kernel version are still probed, as they are necessary for the remaining
probes.

Among the program types, only the ones that can be offloaded are
probed. Among the helpers, only the ones that can work with the latter
program types are probed. All map types are probed, as there is no
specific rule telling which one could or could not be supported by a
device in the future.

Caveat: as bpftool does not attempt to attach programs to the device at
the moment, probes do not entirely reflect what the device accepts:
typically, for Netronome's nfp, results will announce that TC cls
offload is available even if support has been deactivated (with e.g.
ethtool -K eth1 hw-tc-offload off).

Signed-off-by: Quentin Monnet <quentin.monnet@netronome.com>
Reviewed-by: Jakub Kicinski <jakub.kicinski@netronome.com>
---
 .../bpftool/Documentation/bpftool-feature.rst | 18 +++-
 tools/bpf/bpftool/common.c                    |  2 +-
 tools/bpf/bpftool/feature.c                   | 97 ++++++++++++++++---
 tools/bpf/bpftool/main.h                      |  1 +
 4 files changed, 103 insertions(+), 15 deletions(-)
diff mbox series

Patch

diff --git a/tools/bpf/bpftool/Documentation/bpftool-feature.rst b/tools/bpf/bpftool/Documentation/bpftool-feature.rst
index 083d30510cce..9f366461fb72 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-feature.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-feature.rst
@@ -19,16 +19,21 @@  SYNOPSIS
 MAP COMMANDS
 =============
 
-|	**bpftool** **feature probe** [**kernel**] [**macros** [**prefix** *PREFIX*]]
+|	**bpftool** **feature probe** [*COMPONENT*] [**macros** [**prefix** *PREFIX*]]
 |	**bpftool** **feature help**
+|
+|	*COMPONENT* := { **kernel** | **dev** *NAME* }
 
 DESCRIPTION
 ===========
 	**bpftool feature probe** [**kernel**] [**macros** [**prefix** *PREFIX*]]
 		  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
+		  functions availability, and more.
 
-		  Keyword **kernel** can be omitted.
+		  Keyword **kernel** can be omitted. If no probe target is
+		  specified, probing the kernel is the default behaviour.
 
 		  If the **macros** keyword (but not the **-j** option) is
 		  passed, output is dumped as a list of **#define** macros that
@@ -43,6 +48,13 @@  DESCRIPTION
 		  **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
+		  results to the console.
+
+		  The two keywords **macros** and **prefix** have the same
+		  role as when probing the kernel.
+
 	**bpftool feature help**
 		  Print short help message.
 
diff --git a/tools/bpf/bpftool/common.c b/tools/bpf/bpftool/common.c
index 99e4027dde75..9cd5b24e4778 100644
--- a/tools/bpf/bpftool/common.c
+++ b/tools/bpf/bpftool/common.c
@@ -521,7 +521,7 @@  static int read_sysfs_hex_int(char *path)
 	return strtol(vendor_id_buf, NULL, 0);
 }
 
-static int read_sysfs_netdev_hex_int(char *devname, const char *entry_name)
+int read_sysfs_netdev_hex_int(char *devname, const char *entry_name)
 {
 	char full_path[64];
 
diff --git a/tools/bpf/bpftool/feature.c b/tools/bpf/bpftool/feature.c
index 77221fff6ba9..0283faacf1dc 100644
--- a/tools/bpf/bpftool/feature.c
+++ b/tools/bpf/bpftool/feature.c
@@ -5,6 +5,7 @@ 
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
+#include <net/if.h>
 #include <sys/utsname.h>
 #include <sys/vfs.h>
 
@@ -22,6 +23,7 @@ 
 enum probe_component {
 	COMPONENT_UNSPEC,
 	COMPONENT_KERNEL,
+	COMPONENT_DEVICE,
 };
 
 #define MAX_HELPER_NAME_LEN 32
@@ -616,7 +618,8 @@  static bool probe_bpf_syscall(const char *define_prefix)
 
 static void
 prog_load(enum bpf_prog_type prog_type, const struct bpf_insn *insns,
-	  size_t insns_cnt, int kernel_version, char *buf, size_t buf_len)
+	  size_t insns_cnt, int kernel_version, char *buf, size_t buf_len,
+	  __u32 ifindex)
 {
 	struct bpf_load_program_attr xattr = {};
 	int fd;
@@ -634,6 +637,7 @@  prog_load(enum bpf_prog_type prog_type, const struct bpf_insn *insns,
 	xattr.insns_cnt = insns_cnt;
 	xattr.license = "GPL";
 	xattr.kern_version = kernel_version;
+	xattr.prog_ifindex = ifindex;
 
 	fd = bpf_load_program_xattr(&xattr, buf, buf_len);
 	if (fd >= 0)
@@ -642,7 +646,7 @@  prog_load(enum bpf_prog_type prog_type, const struct bpf_insn *insns,
 
 static void
 probe_prog_type(enum bpf_prog_type prog_type, int kernel_version,
-		bool *supported_types, const char *define_prefix)
+		bool *supported_types, const char *define_prefix, __u32 ifindex)
 {
 	char buf[4096], feat_name[128], define_name[128], plain_desc[128];
 	const char *plain_comment = "eBPF program_type ";
@@ -653,9 +657,22 @@  probe_prog_type(enum bpf_prog_type prog_type, int kernel_version,
 	size_t maxlen;
 	bool res;
 
+	if (ifindex)
+		/* Only test offload-able program types */
+		switch (prog_type) {
+		case BPF_PROG_TYPE_SCHED_CLS:
+			/* nfp returns -EINVAL on exit(0) with TC offload */
+			insns[0].imm = 2;
+			/* fall through */
+		case BPF_PROG_TYPE_XDP:
+			break;
+		default:
+			return;
+		}
+
 	errno = 0;
 	prog_load(prog_type, insns, ARRAY_SIZE(insns), kernel_version,
-		  buf, sizeof(buf));
+		  buf, sizeof(buf), ifindex);
 	res = (errno != EINVAL && errno != EOPNOTSUPP);
 
 	supported_types[prog_type] |= res;
@@ -675,7 +692,8 @@  probe_prog_type(enum bpf_prog_type prog_type, int kernel_version,
 }
 
 static void
-probe_map_type(enum bpf_map_type map_type, const char *define_prefix)
+probe_map_type(enum bpf_map_type map_type, const char *define_prefix,
+	       __u32 ifindex)
 {
 	char feat_name[128], define_name[128], plain_desc[128];
 	int key_size, value_size, max_entries, map_flags;
@@ -716,6 +734,12 @@  probe_map_type(enum bpf_map_type map_type, const char *define_prefix)
 	switch (map_type) {
 	case BPF_MAP_TYPE_ARRAY_OF_MAPS:
 	case BPF_MAP_TYPE_HASH_OF_MAPS:
+		/* TODO: probe for device, once libbpf has an API to create
+		 * map-in-map for offload
+		 */
+		if (ifindex)
+			break;
+
 		fd_inner = bpf_create_map(BPF_MAP_TYPE_HASH,
 					  sizeof(__u32), sizeof(__u32), 1, 0);
 		if (fd_inner < 0)
@@ -731,6 +755,7 @@  probe_map_type(enum bpf_map_type map_type, const char *define_prefix)
 		attr.value_size = value_size;
 		attr.max_entries = max_entries;
 		attr.map_flags = map_flags;
+		attr.map_ifindex = ifindex;
 
 		fd = bpf_create_map_xattr(&attr);
 		break;
@@ -757,7 +782,7 @@  probe_map_type(enum bpf_map_type map_type, const char *define_prefix)
 static void
 probe_helper(__u32 id, enum bpf_prog_type prog_type, const char *name,
 	     int kernel_version, bool *supported_types,
-	     const char *define_prefix)
+	     const char *define_prefix, __u32 ifindex, int vendor_id)
 {
 	char buf[4096], feat_name[128], define_name[128], plain_desc[128];
 	struct bpf_insn insns[2] = {
@@ -766,15 +791,35 @@  probe_helper(__u32 id, enum bpf_prog_type prog_type, const char *name,
 	};
 	bool res = false;
 
+	if (ifindex)
+		/* Only test helpers compatible with offload-able prog types */
+		switch (prog_type) {
+		case BPF_PROG_TYPE_XDP:
+		case BPF_PROG_TYPE_SCHED_CLS:
+			break;
+		default:
+			return;
+		}
+
 	if (!supported_types[prog_type])
 		goto do_print;
 
 	/* Reset buffer in case no debug info was written at previous probe */
 	*buf = '\0';
 	prog_load(prog_type, insns, ARRAY_SIZE(insns), kernel_version,
-		  buf, sizeof(buf));
+		  buf, sizeof(buf), ifindex);
 	res = !grep(buf, "invalid func ") && !grep(buf, "unknown func ");
 
+	if (ifindex)
+		switch (vendor_id) {
+		case 0x19ee: /* Netronome specific */
+			res = res && !grep(buf, "not supported by FW") &&
+				!grep(buf, "unsupported function id");
+			break;
+		default:
+			break;
+		}
+
 do_print:
 	sprintf(feat_name, "have_%s_helper", name);
 	sprintf(define_name, "%s_helper", name);
@@ -790,7 +835,10 @@  static int do_probe(int argc, char **argv)
 	const char *define_prefix = NULL;
 	bool supported_types[128] = {};
 	int kernel_version;
+	__u32 ifindex = 0;
+	int vendor_id = 0;
 	unsigned int i;
+	char *ifname;
 
 	/* Detection assumes user has sufficient privileges (CAP_SYS_ADMIN).
 	 * Let's approximate, and restrict usage to root user only.
@@ -810,6 +858,24 @@  static int do_probe(int argc, char **argv)
 			}
 			target = COMPONENT_KERNEL;
 			NEXT_ARG();
+		} else if (is_prefix(*argv, "dev")) {
+			NEXT_ARG();
+
+			if (target != COMPONENT_UNSPEC || ifindex) {
+				p_err("component to probe already specified");
+				return -1;
+			}
+			if (!REQ_ARGS(1))
+				return -1;
+
+			target = COMPONENT_DEVICE;
+			ifname = GET_ARG();
+			ifindex = if_nametoindex(ifname);
+			if (!ifindex) {
+				p_err("unrecognized netdevice '%s': %s", ifname,
+				      strerror(errno));
+				return -1;
+			}
 		} else if (is_prefix(*argv, "macros") && !define_prefix) {
 			define_prefix = "";
 			NEXT_ARG();
@@ -828,7 +894,7 @@  static int do_probe(int argc, char **argv)
 				return -1;
 			define_prefix = GET_ARG();
 		} else {
-			p_err("expected no more arguments, 'kernel', 'macros' or 'prefix', got: '%s'?",
+			p_err("expected no more arguments, 'kernel', 'dev', 'macros' or 'prefix', got: '%s'?",
 			      *argv);
 			return -1;
 		}
@@ -858,6 +924,8 @@  static int do_probe(int argc, char **argv)
 		else
 			printf("\n");
 		break;
+	default:
+		break;
 	}
 
 	print_start_section("syscall_config",
@@ -865,6 +933,7 @@  static int do_probe(int argc, char **argv)
 			    "Scanning system call and kernel version...",
 			    define_prefix);
 
+	/* Get kernel version in all cases, we need it for kprobe programs */
 	kernel_version = probe_kernel_version(define_prefix);
 	if (!probe_bpf_syscall(define_prefix))
 		/* bpf() syscall unavailable, don't probe other BPF features */
@@ -878,7 +947,7 @@  static int do_probe(int argc, char **argv)
 	for (i = BPF_PROG_TYPE_SOCKET_FILTER;
 	     i < ARRAY_SIZE(prog_type_name); i++)
 		probe_prog_type(i, kernel_version, supported_types,
-				define_prefix);
+				define_prefix, ifindex);
 
 	print_end_then_start_section("map_types",
 				     "/*** eBPF map types ***/",
@@ -886,17 +955,21 @@  static int do_probe(int argc, char **argv)
 				     define_prefix);
 
 	for (i = BPF_MAP_TYPE_HASH; i < map_type_name_size; i++)
-		probe_map_type(i, define_prefix);
+		probe_map_type(i, define_prefix, ifindex);
 
 	print_end_then_start_section("helpers",
 				     "/*** eBPF helper functions ***/",
 				     "Scanning eBPF helper functions...",
 				     define_prefix);
 
+	if (ifindex)
+		vendor_id = read_sysfs_netdev_hex_int(ifname, "vendor");
+
 	for (i = 1; i < ARRAY_SIZE(helper_progtype_and_name); i++)
 		probe_helper(i, helper_progtype_and_name[i].progtype,
 			     helper_progtype_and_name[i].name,
-			     kernel_version, supported_types, define_prefix);
+			     kernel_version, supported_types, define_prefix,
+			     ifindex, vendor_id);
 
 exit_close_json:
 	if (json_output) {
@@ -917,8 +990,10 @@  static int do_help(int argc, char **argv)
 	}
 
 	fprintf(stderr,
-		"Usage: %s %s probe [kernel] [macros [prefix PREFIX]]\n"
+		"Usage: %s %s probe [COMPONENT] [macros [prefix PREFIX]]\n"
 		"       %s %s help\n"
+		"\n"
+		"       COMPONENT := { kernel | dev NAME }\n"
 		"",
 		bin_name, argv[-2], bin_name, argv[-2]);
 
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index d9a2b1eafae7..f8518c7ec2cc 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -181,6 +181,7 @@  void print_hex_data_json(uint8_t *data, size_t len);
 
 unsigned int get_page_size(void);
 unsigned int get_possible_cpus(void);
+int read_sysfs_netdev_hex_int(char *devname, const char *entry_name);
 const char *
 ifindex_to_bfd_params(__u32 ifindex, __u64 ns_dev, __u64 ns_ino,
 		      const char **opt);