BPF updates
Subsystems (3)
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
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
@@ -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
PATCH v2 3/5 {f,m}_bpf: allow for user-defined object pinnings
In reply to Daniel BorkmannThe 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v2 2/5 {f,m}_bpf: check map attributes when fetching as pinned
In reply to Daniel BorkmannMake 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
@@ -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
PATCH v2 4/5 {f,m}_bpf: allow updates on program arrays
In reply to Daniel BorkmannSince 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
@@ -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
@@ -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
PATCH v2 5/5 {f,m}_bpf: add more example code
In reply to Daniel BorkmannI'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
@@ -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
@@ -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
@@ -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
Re: [PATCH iproute2 -next v2 4/5] {f,m}_bpf: allow updates on program arrays
In reply to Daniel BorkmannQuoted 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
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
Re: [PATCH iproute2 -next v2 5/5] {f,m}_bpf: add more example code
In reply to Daniel BorkmannQuoted 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.
Signed-off-by Daniel Borkmann <[email protected]>
All applied to net-next branch.