Skip to contentSkip to conversation

BPF updates

PATCH In review Daniel Borkmann 3 participants 9 messages Nov 26, 2015 – Nov 29, 2015

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

v2 cover BPF updates

Some more updates on the BPF front-end to get further eBPF functionality working with tc. See individual patches for details. Targeted at iproute2's -next branch.

Thanks!

v1 -> v2:
- fix minor stylistic nit spotted by Sergei

Daniel Borkmann (5):
{f,m}_bpf: make tail calls working
{f,m}_bpf: check map attributes when fetching as pinned {f,m}_bpf: allow for user-defined object pinnings {f,m}_bpf: allow updates on program arrays {f,m}_bpf: add more example code

 etc/iproute2/bpf_pinning    |   6 +
 examples/bpf/README         |  13 +
 examples/bpf/bpf_cyclic.c   |  32 ++
 examples/bpf/bpf_funcs.h    |  11 +
 examples/bpf/bpf_graft.c    |  70 +++++
 examples/bpf/bpf_tailcall.c | 115 +++++++
 include/bpf_elf.h           |   2 +-
 include/utils.h             |   4 +
 lib/rt_names.c              |   5 +-
 tc/e_bpf.c                  |  30 +-
 tc/tc_bpf.c                 | 708 +++++++++++++++++++++++++++++++++-----------
 tc/tc_bpf.h                 |   1 +
 12 files changed, 819 insertions(+), 178 deletions(-)
 create mode 100644 etc/iproute2/bpf_pinning
 create mode 100644 examples/bpf/README
 create mode 100644 examples/bpf/bpf_cyclic.c
 create mode 100644 examples/bpf/bpf_graft.c
 create mode 100644 examples/bpf/bpf_tailcall.c
Signature · 1 line
1.9.3
#1 lore raw

PATCH v2 1/5 {f,m}_bpf: make tail calls working

Now that we have the possibility of sharing maps, it's time we get the ELF loader fully working with regards to tail calls. Since program array maps are pinned, we can keep them finally alive. I've noticed two bugs that are being fixed in bpf_fill_prog_arrays() with this patch. Example code comes as follow-up.

---
 tc/tc_bpf.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)
tc/tc_bpf.c +19 -8
index bc7bc9f..c3adc23 100644
@@ -1139,11 +1139,22 @@ static int bpf_fetch_prog_sec(struct bpf_elf_ctx *ctx, const char *section)
 	return ret;
 }
 
+static int bpf_find_map_by_id(struct bpf_elf_ctx *ctx, uint32_t id)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ctx->map_fds); i++)
+		if (ctx->map_fds[i] && ctx->maps[i].id == id &&
+		    ctx->maps[i].type == BPF_MAP_TYPE_PROG_ARRAY)
+			return i;
+	return -1;
+}
+
 static int bpf_fill_prog_arrays(struct bpf_elf_ctx *ctx)
 {
 	struct bpf_elf_sec_data data;
 	uint32_t map_id, key_id;
-	int fd, i, ret;
+	int fd, i, ret, idx;
 
 	for (i = 1; i < ctx->elf_hdr.e_shnum; i++) {
 		if (ctx->sec_done[i])
@@ -1153,20 +1164,20 @@ static int bpf_fill_prog_arrays(struct bpf_elf_ctx *ctx)
 		if (ret < 0)
 			continue;
 
-		ret = sscanf(data.sec_name, "%u/%u", &map_id, &key_id);
-		if (ret != 2 || map_id >= ARRAY_SIZE(ctx->map_fds) ||
-		    !ctx->map_fds[map_id])
+		ret = sscanf(data.sec_name, "%i/%i", &map_id, &key_id);
+		if (ret != 2)
 			continue;
-		if (ctx->maps[map_id].type != BPF_MAP_TYPE_PROG_ARRAY ||
-		    ctx->maps[map_id].max_elem <= key_id)
+
+		idx = bpf_find_map_by_id(ctx, map_id);
+		if (idx < 0)
 			continue;
 
 		fd = bpf_fetch_prog_sec(ctx, data.sec_name);
 		if (fd < 0)
 			return -EIO;
 
-		ret = bpf_map_update(ctx->map_fds[map_id], &key_id,
-				     &fd, BPF_NOEXIST);
+		ret = bpf_map_update(ctx->map_fds[idx], &key_id,
+				     &fd, BPF_ANY);
 		if (ret < 0)
 			return -ENOENT;
 
Signature · 1 line
1.9.3
#2 lore raw

PATCH v2 2/5 {f,m}_bpf: check map attributes when fetching as pinned

In reply to Daniel Borkmann

Make use of the new show_fdinfo() facility and verify that when a pinned map is being fetched that its basic attributes are the same as the map we declared from the ELF file. I.e. when placed into the globalns, collisions could occur. In such a case warn the user and bail out.

---
 tc/tc_bpf.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)
tc/tc_bpf.c +53 -0
index c3adc23..b44b123 100644
@@ -205,6 +205,52 @@ void bpf_print_ops(FILE *f, struct rtattr *bpf_ops, __u16 len)
 		ops[i].jf, ops[i].k);
 }
 
+static int bpf_map_selfcheck_pinned(int fd, const struct bpf_elf_map *map)
+{
+	char file[PATH_MAX], buff[4096];
+	struct bpf_elf_map tmp, zero;
+	unsigned int val;
+	FILE *fp;
+
+	snprintf(file, sizeof(file), "/proc/%d/fdinfo/%d", getpid(), fd);
+
+	fp = fopen(file, "r");
+	if (!fp) {
+		fprintf(stderr, "No procfs support?!\n");
+		return -EIO;
+	}
+
+	memset(&tmp, 0, sizeof(tmp));
+	while (fgets(buff, sizeof(buff), fp)) {
+		if (sscanf(buff, "map_type:\t%u", &val) == 1)
+			tmp.type = val;
+		else if (sscanf(buff, "key_size:\t%u", &val) == 1)
+			tmp.size_key = val;
+		else if (sscanf(buff, "value_size:\t%u", &val) == 1)
+			tmp.size_value = val;
+		else if (sscanf(buff, "max_entries:\t%u", &val) == 1)
+			tmp.max_elem = val;
+	}
+
+	fclose(fp);
+
+	if (!memcmp(&tmp, map, offsetof(struct bpf_elf_map, id))) {
+		return 0;
+	} else {
+		memset(&zero, 0, sizeof(zero));
+		/* If kernel doesn't have eBPF-related fdinfo, we cannot do much,
+		 * so just accept it. We know we do have an eBPF fd and in this
+		 * case, everything is 0. It is guaranteed that no such map exists
+		 * since map type of 0 is unloadable BPF_MAP_TYPE_UNSPEC.
+		 */
+		if (!memcmp(&tmp, &zero, offsetof(struct bpf_elf_map, id)))
+			return 0;
+
+		fprintf(stderr, "Map specs from pinned file differ!\n");
+		return -EINVAL;
+	}
+}
+
 static int bpf_valid_mntpt(const char *mnt, unsigned long magic)
 {
 	struct statfs st_fs;
@@ -816,6 +862,13 @@ static int bpf_map_attach(const char *name, const struct bpf_elf_map *map,
 
 	fd = bpf_probe_pinned(name, map->pinning);
 	if (fd > 0) {
+		ret = bpf_map_selfcheck_pinned(fd, map);
+		if (ret < 0) {
+			close(fd);
+			fprintf(stderr, "Map \'%s\' self-check failed!\n",
+				name);
+			return ret;
+		}
 		if (verbose)
 			fprintf(stderr, "Map \'%s\' loaded as pinned!\n",
 				name);
Signature · 1 line
1.9.3
#3 lore raw

PATCH v2 3/5 {f,m}_bpf: allow for user-defined object pinnings

In reply to Daniel Borkmann

The recently introduced object pinning can be further extended in order to allow sharing maps beyond tc namespace. F.e. maps that are being pinned from tracing side, can be accessed through this facility as well.

---
 etc/iproute2/bpf_pinning |   6 ++
 include/bpf_elf.h        |   2 +-
 include/utils.h          |   4 +
 lib/rt_names.c           |   5 +-
 tc/tc_bpf.c              | 212 ++++++++++++++++++++++++++++++++++++++++++-----
 5 files changed, 204 insertions(+), 25 deletions(-)
 create mode 100644 etc/iproute2/bpf_pinning
etc/iproute2/bpf_pinning new file +6 -0
new file mode 100644
index 0000000..2b39c70
@@ -0,0 +1,6 @@
+#
+# subpath mappings from mount point for pinning
+#
+#3	tracing
+#4	foo/bar
+#5	tc/cls1
include/bpf_elf.h +1 -1
index 0690dd6..31a8974 100644
@@ -33,7 +33,7 @@ struct bpf_elf_map {
 	__u32 size_value;
 	__u32 max_elem;
 	__u32 id;
-	__u8  pinning;
+	__u32 pinning;
 };
 
 #endif /* __BPF_ELF__ */
include/utils.h +4 -0
index 5902a98..e830be6 100644
@@ -40,6 +40,10 @@ extern bool do_all;
 #define IPSEC_PROTO_ANY	255
 #endif
 
+#ifndef CONFDIR
+#define CONFDIR		"/etc/iproute2"
+#endif
+
 #define SPRINT_BSIZE 64
 #define SPRINT_BUF(x)	char x[SPRINT_BSIZE]
 
lib/rt_names.c +1 -4
index e87c65d..3968c76 100644
@@ -22,10 +22,7 @@
 #include <linux/rtnetlink.h>
 
 #include "rt_names.h"
-
-#ifndef CONFDIR
-#define CONFDIR "/etc/iproute2"
-#endif
+#include "utils.h"
 
 #define NAME_MAX_LEN 512
 
tc/tc_bpf.c +192 -20

308 lines not on this page · raw message

Signature · 1 line
1.9.3
#4 lore raw

PATCH v2 4/5 {f,m}_bpf: allow updates on program arrays

In reply to Daniel Borkmann

Since we have all infrastructure in place now, allow atomic live updates on program arrays. This can be very useful e.g. in case programs that are being tail-called need to be replaced, f.e. when classifier functionality needs to be changed, new protocols added/removed during runtime, etc.

Thus, provide a way for in-place code updates, minimal example: Given is an object file cls.o that contains the entry point in section 'classifier', has a globally pinned program array 'jmp' with 2 slots and id of 0, and two tail called programs under section '0/0' (prog array key 0) and '0/1' (prog array key 1), the section encoding for the loader is <id/key>. Adding the filter loads everything into cls_bpf:

tc filter add dev foo parent ffff: bpf da obj cls.o

Now, the program under section '0/1' needs to be replaced with an updated version that resides in the same section (also full path to tc's subfolder of the mount point can be passed, e.g. /sys/fs/bpf/tc/globals/jmp):

tc exec bpf graft m:globals/jmp obj cls.o sec 0/1

In case the program resides under a different section 'foo', it can also be injected into the program array like:

tc exec bpf graft m:globals/jmp key 1 obj cls.o sec foo

If the new tail called classifier program is already available as a pinned object somewhere (here: /sys/fs/bpf/tc/progs/parser), it can be injected into the prog array like:

tc exec bpf graft m:globals/jmp key 1 fd m:progs/parser

In the kernel, the program on key 1 is being atomically replaced and the old one's refcount dropped.

---
 tc/e_bpf.c  |  30 ++++-
 tc/tc_bpf.c | 424 +++++++++++++++++++++++++++++++++++++++---------------------
 tc/tc_bpf.h |   1 +
 3 files changed, 306 insertions(+), 149 deletions(-)
tc/e_bpf.c +29 -1
index 1f386c3..2d650a4 100644
@@ -26,10 +26,19 @@ static char *argv_default[] = { BPF_DEFAULT_CMD, NULL };
 
 static void explain(void)
 {
-	fprintf(stderr, "Usage: ... bpf [ import UDS_FILE ] [ run CMD ] [ debug ]\n\n");
+	fprintf(stderr, "Usage: ... bpf [ import UDS_FILE ] [ run CMD ]\n");
+	fprintf(stderr, "       ... bpf [ debug ]\n");
+	fprintf(stderr, "       ... bpf [ graft MAP_FILE ] [ key KEY ]\n");
+	fprintf(stderr, "          `... [ object-file OBJ_FILE ] [ type TYPE ] [ section NAME ] [ verbose ]\n");
+	fprintf(stderr, "          `... [ object-pinned PROG_FILE ]\n");
+	fprintf(stderr, "\n");
 	fprintf(stderr, "Where UDS_FILE provides the name of a unix domain socket file\n");
 	fprintf(stderr, "to import eBPF maps and the optional CMD denotes the command\n");
 	fprintf(stderr, "to be executed (default: \'%s\').\n", BPF_DEFAULT_CMD);
+	fprintf(stderr, "Where MAP_FILE points to a pinned map, OBJ_FILE to an object file\n");
+	fprintf(stderr, "and PROG_FILE to a pinned program. TYPE can be {cls, act}, where\n");
+	fprintf(stderr, "\'cls\' is default. KEY is optional and can be inferred from the\n");
+	fprintf(stderr, "section name, otherwise it needs to be provided.\n");
 }
 
 static int bpf_num_env_entries(void)
@@ -67,6 +76,25 @@ static int parse_bpf(struct exec_util *eu, int argc, char **argv)
 				fprintf(stderr,
 					"No trace pipe, tracefs not mounted?\n");
 			return -1;
+		} else if (matches(*argv, "graft") == 0) {
+			const char *bpf_map_path;
+			bool has_key = false;
+			uint32_t key;
+
+			NEXT_ARG();
+			bpf_map_path = *argv;
+			NEXT_ARG();
+			if (matches(*argv, "key") == 0) {
+				NEXT_ARG();
+				if (get_unsigned(&key, *argv, 0)) {
+					fprintf(stderr, "Illegal \"key\"\n");
+					return -1;
+				}
+				has_key = true;
+				NEXT_ARG();
+			}
+			return bpf_graft_map(bpf_map_path, has_key ?
+					     &key : NULL, argc, argv);
 		} else {
 			explain();
 			return -1;
tc/tc_bpf.c +276 -148

521 lines not on this page · raw message

tc/tc_bpf.h +1 -0
index dea3c3b..526d0b1 100644
@@ -55,6 +55,7 @@ const char *bpf_default_section(const enum bpf_prog_type type);
 int bpf_parse_common(int *ptr_argc, char ***ptr_argv, const int *nla_tbl,
 		     enum bpf_prog_type type, const char **ptr_object,
 		     const char **ptr_uds_name, struct nlmsghdr *n);
+int bpf_graft_map(const char *map_path, uint32_t *key, int argc, char **argv);
 
 void bpf_print_ops(FILE *f, struct rtattr *bpf_ops, __u16 len);
 
Signature · 1 line
1.9.3
#5 lore raw

Re: [PATCH iproute2 -next v2 4/5] {f,m}_bpf: allow updates on program arrays

Quoted 15 lines · Daniel Borkmann
tc/tc_bpf.c +7 -0
+static int bpf_mnt_fs(const char *target)
+{
+       bool bind_done = false;
+
+       while (mount("", target, "none", MS_PRIVATE | MS_REC, NULL)) {
+               if (errno != EINVAL || bind_done) {
+                       fprintf(stderr, "mount --make-private %s failed:

%s\n",

tc/tc_bpf.c +6 -0
+                               target, strerror(errno));
+                       return -1;
+               }
+
+               if (mount(target, target, "none", MS_BIND, NULL)) {
+                       fprintf(stderr, "mount --bind %s %s failed:

%s\n",

tc/tc_bpf.c static int bpf_map_selfcheck_pinned(int fd, const struct bpf_elf_map *map) · line 277

Quoted 6 lines
tc/tc_bpf.c +6 -0
+                               target, target, strerror(errno));
+                       return -1;
+               }
+
+               bind_done = true;
+       }

Why does user space actually still have to deal with setting the mount point private? Isn't this handled by the kernel?

tc/tc_bpf.c static int bpf_map_selfcheck_pinned(int fd, const struct bpf_elf_map *map) · line 283

Quoted 5 lines
tc/tc_bpf.c +5 -0
+       if (mount("bpf", target, "bpf", 0, NULL)) {
+               fprintf(stderr, "mount -t bpf bpf %s failed: %s\n",
+                       target, strerror(errno));
+               return -1;
+       }

Shouldn't this be just enough?

Review

Quoted 2 lines
(quoted diff) +2 -0
+       return 0;
+}

Thanks,
Hannes

#6 lore raw

Re: [PATCH iproute2 -next v2 4/5] {f,m}_bpf: allow updates on program arrays

Quoted 1 line · Hannes Frederic Sowa

On Thu, Nov 26, 2015, at 15:38, Daniel Borkmann wrote:

[...]

Quoted 10 lines

Why does user space actually still have to deal with setting the mount point private? Isn't this handled by the kernel?

tc/tc_bpf.c static int bpf_map_selfcheck_pinned(int fd, const struct bpf_elf_map *map) · line 283

Quoted 5 lines
tc/tc_bpf.c +5 -0
+       if (mount("bpf", target, "bpf", 0, NULL)) {
+               fprintf(stderr, "mount -t bpf bpf %s failed: %s\n",
+                       target, strerror(errno));
+               return -1;
+       }

Shouldn't this be just enough?

Note that the patch just moves the function around, but to get to your question, that would just make it shared by default, not private.

Thanks,
Daniel

#7 lore raw

PATCH v2 5/5 {f,m}_bpf: add more example code

In reply to Daniel Borkmann

I've added three examples to examples/bpf/ that demonstrate how one can implement eBPF tail calls in tc with f.e. multiple levels of nesting. That should act as a good starting point, but also as test cases for the ELF loader and kernel. A real test suite for {f,m,e}_bpf is still to be developed in future work.

---
 examples/bpf/README         |  13 +++++
 examples/bpf/bpf_cyclic.c   |  32 ++++++++++++
 examples/bpf/bpf_funcs.h    |  11 +++++
 examples/bpf/bpf_graft.c    |  70 +++++++++++++++++++++++++++
 examples/bpf/bpf_tailcall.c | 115 ++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 241 insertions(+)
 create mode 100644 examples/bpf/README
 create mode 100644 examples/bpf/bpf_cyclic.c
 create mode 100644 examples/bpf/bpf_graft.c
 create mode 100644 examples/bpf/bpf_tailcall.c
examples/bpf/README new file +13 -0
new file mode 100644
index 0000000..4247257
@@ -0,0 +1,13 @@
+eBPF toy code examples (running in kernel) to familiarize yourself
+with syntax and features:
+
+ - bpf_prog.c		-> Classifier examples with using maps
+ - bpf_shared.c		-> Ingress/egress map sharing example
+ - bpf_tailcall.c	-> Using tail call chains
+ - bpf_cyclic.c		-> Simple cycle as tail calls
+ - bpf_graft.c		-> Demo on altering runtime behaviour
+
+User space code example:
+
+ - bpf_agent.c		-> Counterpart to bpf_prog.c for user
+                           space to transfer/read out map data
examples/bpf/bpf_cyclic.c new file +32 -0
new file mode 100644
index 0000000..bde061c
@@ -0,0 +1,32 @@
+#include <linux/bpf.h>
+
+#include "bpf_funcs.h"
+
+/* Cyclic dependency example to test the kernel's runtime upper
+ * bound on loops.
+ */
+struct bpf_elf_map __section("maps") jmp_tc = {
+	.type		= BPF_MAP_TYPE_PROG_ARRAY,
+	.id		= 0xabccba,
+	.size_key	= sizeof(int),
+	.size_value	= sizeof(int),
+	.pinning	= PIN_OBJECT_NS,
+	.max_elem	= 1,
+};
+
+__section_tail(0xabccba, 0) int cls_loop(struct __sk_buff *skb)
+{
+	char fmt[] = "cb: %u\n";
+
+	bpf_printk(fmt, sizeof(fmt), skb->cb[0]++);
+	bpf_tail_call(skb, &jmp_tc, 0);
+	return -1;
+}
+
+__section("classifier") int cls_entry(struct __sk_buff *skb)
+{
+	bpf_tail_call(skb, &jmp_tc, 0);
+	return -1;
+}
+
+char __license[] __section("license") = "GPL";
examples/bpf/bpf_funcs.h +11 -0
index 1369401..6d058f0 100644
@@ -10,10 +10,18 @@
 # define __maybe_unused		__attribute__ ((__unused__))
 #endif
 
+#ifndef __stringify
+# define __stringify(x)		#x
+#endif
+
 #ifndef __section
 # define __section(NAME)	__attribute__((section(NAME), used))
 #endif
 
+#ifndef __section_tail
+# define __section_tail(m, x)	__section(__stringify(m) "/" __stringify(x))
+#endif
+
 #ifndef offsetof
 # define offsetof		__builtin_offsetof
 #endif
@@ -50,6 +58,9 @@ static unsigned int (*get_prandom_u32)(void) __maybe_unused =
 static int (*bpf_printk)(const char *fmt, int fmt_size, ...) __maybe_unused =
 	(void *) BPF_FUNC_trace_printk;
 
+static void (*bpf_tail_call)(void *ctx, void *map, int index) __maybe_unused =
+	(void *) BPF_FUNC_tail_call;
+
 /* LLVM built-in functions that an eBPF C program may use to emit
  * BPF_LD_ABS and BPF_LD_IND instructions.
  */
examples/bpf/bpf_graft.c new file +70 -0

70 lines not on this page · raw message

examples/bpf/bpf_tailcall.c new file +115 -0

115 lines not on this page · raw message

Signature · 1 line
1.9.3
#8 lore raw

Re: [PATCH iproute2 -next v2 5/5] {f,m}_bpf: add more example code

Quoted 8 lines · On Thu, 26 Nov 2015 15:38:46 +0100 Daniel Borkmann <[email protected]> wrote:

I've added three examples to examples/bpf/ that demonstrate how one can implement eBPF tail calls in tc with f.e. multiple levels of nesting. That should act as a good starting point, but also as test cases for the ELF loader and kernel. A real test suite for {f,m,e}_bpf is still to be developed in future work.

All applied to net-next branch.