diff options
author | John Fastabend <john.fastabend@gmail.com> | 2017-08-15 22:34:22 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2017-08-16 11:27:53 -0700 |
commit | 6f6d33f3b3d0f53799d120d28abd13ad90041549 (patch) | |
tree | dbd263882c3bc49c8bc1d7a08b7d0eadd0f4ddb3 | |
parent | 41bc94f535ef454e325a6d4db085ec345376de6c (diff) | |
download | linux-6f6d33f3b3d0f53799d120d28abd13ad90041549.tar.gz linux-6f6d33f3b3d0f53799d120d28abd13ad90041549.tar.bz2 linux-6f6d33f3b3d0f53799d120d28abd13ad90041549.zip |
bpf: selftests add sockmap tests
This generates a set of sockets, attaches BPF programs, and sends some
simple traffic using basic send/recv pattern. Additionally, we do a bunch
of negative tests to ensure adding/removing socks out of the sockmap fail
correctly.
Signed-off-by: John Fastabend <john.fastabend@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | tools/lib/bpf/libbpf.c | 29 | ||||
-rw-r--r-- | tools/lib/bpf/libbpf.h | 2 | ||||
-rw-r--r-- | tools/testing/selftests/bpf/Makefile | 2 | ||||
-rw-r--r-- | tools/testing/selftests/bpf/sockmap_parse_prog.c | 38 | ||||
-rw-r--r-- | tools/testing/selftests/bpf/sockmap_verdict_prog.c | 48 | ||||
-rw-r--r-- | tools/testing/selftests/bpf/test_maps.c | 308 | ||||
-rw-r--r-- | tools/testing/selftests/bpf/test_progs.c | 55 |
7 files changed, 443 insertions, 39 deletions
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 1a2c07eb7795..1cc3ea0ffdc3 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -1744,3 +1744,32 @@ long libbpf_get_error(const void *ptr) return PTR_ERR(ptr); return 0; } + +int bpf_prog_load(const char *file, enum bpf_prog_type type, + struct bpf_object **pobj, int *prog_fd) +{ + struct bpf_program *prog; + struct bpf_object *obj; + int err; + + obj = bpf_object__open(file); + if (IS_ERR(obj)) + return -ENOENT; + + prog = bpf_program__next(NULL, obj); + if (!prog) { + bpf_object__close(obj); + return -ENOENT; + } + + bpf_program__set_type(prog, type); + err = bpf_object__load(obj); + if (err) { + bpf_object__close(obj); + return -EINVAL; + } + + *pobj = obj; + *prog_fd = bpf_program__fd(prog); + return 0; +} diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index 32c7252f734e..7959086eb9c9 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -243,4 +243,6 @@ int bpf_map__pin(struct bpf_map *map, const char *path); long libbpf_get_error(const void *ptr); +int bpf_prog_load(const char *file, enum bpf_prog_type type, + struct bpf_object **pobj, int *prog_fd); #endif diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 3c2e67da4b41..f4b23d697448 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -15,7 +15,7 @@ TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test test_align TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \ - test_pkt_md_access.o test_xdp_redirect.o + test_pkt_md_access.o test_xdp_redirect.o sockmap_parse_prog.o sockmap_verdict_prog.o TEST_PROGS := test_kmod.sh test_xdp_redirect.sh diff --git a/tools/testing/selftests/bpf/sockmap_parse_prog.c b/tools/testing/selftests/bpf/sockmap_parse_prog.c new file mode 100644 index 000000000000..8b5453158399 --- /dev/null +++ b/tools/testing/selftests/bpf/sockmap_parse_prog.c @@ -0,0 +1,38 @@ +#include <linux/bpf.h> +#include "bpf_helpers.h" +#include "bpf_util.h" +#include "bpf_endian.h" + +int _version SEC("version") = 1; + +#define bpf_printk(fmt, ...) \ +({ \ + char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), \ + ##__VA_ARGS__); \ +}) + +SEC("sk_skb1") +int bpf_prog1(struct __sk_buff *skb) +{ + void *data_end = (void *)(long) skb->data_end; + void *data = (void *)(long) skb->data; + __u32 lport = skb->local_port; + __u32 rport = skb->remote_port; + char *d = data; + + if (data + 8 > data_end) + return skb->len; + + /* This write/read is a bit pointless but tests the verifier and + * strparser handler for read/write pkt data and access into sk + * fields. + */ + d[0] = 1; + + bpf_printk("data[0] = (%u): local_port %i remote %i\n", + d[0], lport, bpf_ntohl(rport)); + return skb->len; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/sockmap_verdict_prog.c b/tools/testing/selftests/bpf/sockmap_verdict_prog.c new file mode 100644 index 000000000000..d5f9447b3808 --- /dev/null +++ b/tools/testing/selftests/bpf/sockmap_verdict_prog.c @@ -0,0 +1,48 @@ +#include <linux/bpf.h> +#include "bpf_helpers.h" +#include "bpf_util.h" +#include "bpf_endian.h" + +int _version SEC("version") = 1; + +#define bpf_printk(fmt, ...) \ +({ \ + char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), \ + ##__VA_ARGS__); \ +}) + +struct bpf_map_def SEC("maps") sock_map = { + .type = BPF_MAP_TYPE_SOCKMAP, + .key_size = sizeof(int), + .value_size = sizeof(int), + .max_entries = 20, +}; + +SEC("sk_skb2") +int bpf_prog2(struct __sk_buff *skb) +{ + void *data_end = (void *)(long) skb->data_end; + void *data = (void *)(long) skb->data; + __u32 lport = skb->local_port; + __u32 rport = skb->remote_port; + char *d = data; + + if (data + 8 > data_end) + return SK_DROP; + + d[0] = 0xd; + d[1] = 0xe; + d[2] = 0xa; + d[3] = 0xd; + d[4] = 0xb; + d[5] = 0xe; + d[6] = 0xe; + d[7] = 0xf; + + bpf_printk("data[0] = (%u): local_port %i remote %i\n", + d[0], lport, bpf_ntohl(rport)); + return bpf_sk_redirect_map(&sock_map, 5, 0); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/test_maps.c b/tools/testing/selftests/bpf/test_maps.c index c991ab69a720..40b2d1faf02b 100644 --- a/tools/testing/selftests/bpf/test_maps.c +++ b/tools/testing/selftests/bpf/test_maps.c @@ -22,6 +22,7 @@ #include <linux/bpf.h> #include <bpf/bpf.h> +#include <bpf/libbpf.h> #include "bpf_util.h" static int map_flags; @@ -453,6 +454,312 @@ static void test_devmap(int task, void *data) close(fd); } +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <sys/select.h> +#include <linux/err.h> +#define SOCKMAP_PARSE_PROG "./sockmap_parse_prog.o" +#define SOCKMAP_VERDICT_PROG "./sockmap_verdict_prog.o" +static void test_sockmap(int task, void *data) +{ + int ports[] = {50200, 50201, 50202, 50204}; + int err, i, fd, sfd[6] = {0xdeadbeef}; + char buf[] = "hello sockmap user\n"; + int one = 1, map_fd, s, sc, rc; + int parse_prog, verdict_prog; + struct bpf_map *bpf_map; + struct sockaddr_in addr; + struct bpf_object *obj; + struct timeval to; + __u32 key, value; + fd_set w; + + /* Create some sockets to use with sockmap */ + for (i = 0; i < 2; i++) { + sfd[i] = socket(AF_INET, SOCK_STREAM, 0); + if (sfd[i] < 0) + goto out; + err = setsockopt(sfd[i], SOL_SOCKET, SO_REUSEADDR, + (char *)&one, sizeof(one)); + if (err) { + printf("failed to setsockopt\n"); + goto out; + } + err = ioctl(sfd[i], FIONBIO, (char *)&one); + if (err < 0) { + printf("failed to ioctl\n"); + goto out; + } + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(ports[i]); + err = bind(sfd[i], (struct sockaddr *)&addr, sizeof(addr)); + if (err < 0) { + printf("failed to bind: err %i: %i:%i\n", + err, i, sfd[i]); + goto out; + } + err = listen(sfd[i], 32); + if (err < 0) { + printf("failed to listeen\n"); + goto out; + } + } + + for (i = 2; i < 4; i++) { + sfd[i] = socket(AF_INET, SOCK_STREAM, 0); + if (sfd[i] < 0) + goto out; + err = setsockopt(sfd[i], SOL_SOCKET, SO_REUSEADDR, + (char *)&one, sizeof(one)); + if (err) { + printf("set sock opt\n"); + goto out; + } + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(ports[i - 2]); + err = connect(sfd[i], (struct sockaddr *)&addr, sizeof(addr)); + if (err) { + printf("failed to conenct\n"); + goto out; + } + } + + + for (i = 4; i < 6; i++) { + sfd[i] = accept(sfd[i - 4], NULL, NULL); + if (sfd[i] < 0) { + printf("accept failed\n"); + goto out; + } + } + + /* Test sockmap with connected sockets */ + fd = bpf_create_map(BPF_MAP_TYPE_SOCKMAP, + sizeof(key), sizeof(value), + 6, 0); + if (fd < 0) { + printf("Failed to create sockmap %i\n", fd); + goto out_sockmap; + } + + /* Nothing attached so these should fail */ + for (i = 0; i < 6; i++) { + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_ANY); + if (!err) { + printf("Failed invalid update sockmap '%i:%i'\n", + i, sfd[i]); + goto out_sockmap; + } + } + + /* Test attaching bad fds */ + err = __bpf_prog_attach(-1, -2, fd, BPF_CGROUP_SMAP_INGRESS, 0); + if (!err) { + printf("Failed invalid prog attach\n"); + goto out_sockmap; + } + + /* Load SK_SKB program and Attach */ + err = bpf_prog_load(SOCKMAP_PARSE_PROG, + BPF_PROG_TYPE_SK_SKB, &obj, &parse_prog); + if (err) { + printf("Failed to load SK_SKB parse prog\n"); + goto out_sockmap; + } + + err = bpf_prog_load(SOCKMAP_VERDICT_PROG, + BPF_PROG_TYPE_SK_SKB, &obj, &verdict_prog); + if (err) { + printf("Failed to load SK_SKB verdict prog\n"); + goto out_sockmap; + } + + bpf_map = bpf_object__find_map_by_name(obj, "sock_map"); + if (IS_ERR(bpf_map)) { + printf("Failed to load map from verdict prog\n"); + goto out_sockmap; + } + + map_fd = bpf_map__fd(bpf_map); + if (map_fd < 0) { + printf("Failed to get map fd\n"); + goto out_sockmap; + } + + err = __bpf_prog_attach(parse_prog, verdict_prog, map_fd, + BPF_CGROUP_SMAP_INGRESS, 0); + if (err) { + printf("Failed bpf prog attach\n"); + goto out_sockmap; + } + + /* Test map update elem */ + for (i = 0; i < 6; i++) { + err = bpf_map_update_elem(map_fd, &i, &sfd[i], BPF_ANY); + if (err) { + printf("Failed map_fd update sockmap %i '%i:%i'\n", + err, i, sfd[i]); + goto out_sockmap; + } + } + + /* Test map delete elem and remove send/recv sockets */ + for (i = 2; i < 4; i++) { + err = bpf_map_delete_elem(map_fd, &i); + if (err) { + printf("Failed delete sockmap %i '%i:%i'\n", + err, i, sfd[i]); + goto out_sockmap; + } + } + + /* Test map send/recv */ + sc = send(sfd[2], buf, 10, 0); + if (sc < 0) { + printf("Failed sockmap send\n"); + goto out_sockmap; + } + + FD_ZERO(&w); + FD_SET(sfd[3], &w); + to.tv_sec = 1; + to.tv_usec = 0; + s = select(sfd[3] + 1, &w, NULL, NULL, &to); + if (s == -1) { + perror("Failed sockmap select()"); + goto out_sockmap; + } else if (!s) { + printf("Failed sockmap unexpected timeout\n"); + goto out_sockmap; + } + + if (!FD_ISSET(sfd[3], &w)) { + printf("Failed sockmap select/recv\n"); + goto out_sockmap; + } + + rc = recv(sfd[3], buf, sizeof(buf), 0); + if (rc < 0) { + printf("Failed sockmap recv\n"); + goto out_sockmap; + } + + /* Delete the reset of the elems include some NULL elems */ + for (i = 0; i < 6; i++) { + err = bpf_map_delete_elem(map_fd, &i); + if (err && (i == 0 || i == 1 || i >= 4)) { + printf("Failed delete sockmap %i '%i:%i'\n", + err, i, sfd[i]); + goto out_sockmap; + } else if (!err && (i == 2 || i == 3)) { + printf("Failed null delete sockmap %i '%i:%i'\n", + err, i, sfd[i]); + goto out_sockmap; + } + } + + /* Test having multiple SMAPs open and active on same fds */ + err = __bpf_prog_attach(parse_prog, verdict_prog, fd, + BPF_CGROUP_SMAP_INGRESS, 0); + if (err) { + printf("Failed fd bpf prog attach\n"); + goto out_sockmap; + } + + for (i = 0; i < 6; i++) { + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_ANY); + if (err) { + printf("Failed fd update sockmap %i '%i:%i'\n", + err, i, sfd[i]); + goto out_sockmap; + } + } + + /* Test duplicate socket add of NOEXIST, ANY and EXIST */ + i = 0; + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_NOEXIST); + if (!err) { + printf("Failed BPF_NOEXIST create\n"); + goto out_sockmap; + } + + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_ANY); + if (err) { + printf("Failed sockmap update BPF_ANY\n"); + goto out_sockmap; + } + + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_EXIST); + if (err) { + printf("Failed sockmap update BPF_EXIST\n"); + goto out_sockmap; + } + + /* The above were pushing fd into same slot try different slot now */ + i = 2; + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_NOEXIST); + if (!err) { + printf("Failed BPF_NOEXIST create\n"); + goto out_sockmap; + } + + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_ANY); + if (err) { + printf("Failed sockmap update BPF_ANY\n"); + goto out_sockmap; + } + + err = bpf_map_update_elem(fd, &i, &sfd[i], BPF_EXIST); + if (err) { + printf("Failed sockmap update BPF_EXIST\n"); + goto out_sockmap; + } + + /* Try pushing fd into different map, this is not allowed at the + * moment. Which programs would we use? + */ + err = bpf_map_update_elem(map_fd, &i, &sfd[i], BPF_NOEXIST); + if (!err) { + printf("Failed BPF_NOEXIST create\n"); + goto out_sockmap; + } + + err = bpf_map_update_elem(map_fd, &i, &sfd[i], BPF_ANY); + if (!err) { + printf("Failed sockmap update BPF_ANY\n"); + goto out_sockmap; + } + + err = bpf_map_update_elem(map_fd, &i, &sfd[i], BPF_EXIST); + if (!err) { + printf("Failed sockmap update BPF_EXIST\n"); + goto out_sockmap; + } + + /* Test map close sockets */ + for (i = 0; i < 6; i++) + close(sfd[i]); + close(fd); + close(map_fd); + bpf_object__close(obj); + return; +out: + for (i = 0; i < 6; i++) + close(sfd[i]); + printf("Failed to create sockmap '%i:%s'!\n", i, strerror(errno)); + exit(1); +out_sockmap: + for (i = 0; i < 6; i++) + close(sfd[i]); + close(fd); + exit(1); +} + #define MAP_SIZE (32 * 1024) static void test_map_large(void) @@ -621,6 +928,7 @@ static void run_all_tests(void) test_arraymap_percpu_many_keys(); test_devmap(0, NULL); + test_sockmap(0, NULL); test_map_large(); test_map_parallel(); diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c index 1f7dd35551b9..1cb037803679 100644 --- a/tools/testing/selftests/bpf/test_progs.c +++ b/tools/testing/selftests/bpf/test_progs.c @@ -75,39 +75,6 @@ static struct { __ret; \ }) -static int bpf_prog_load(const char *file, enum bpf_prog_type type, - struct bpf_object **pobj, int *prog_fd) -{ - struct bpf_program *prog; - struct bpf_object *obj; - int err; - - obj = bpf_object__open(file); - if (IS_ERR(obj)) { - error_cnt++; - return -ENOENT; - } - - prog = bpf_program__next(NULL, obj); - if (!prog) { - bpf_object__close(obj); - error_cnt++; - return -ENOENT; - } - - bpf_program__set_type(prog, type); - err = bpf_object__load(obj); - if (err) { - bpf_object__close(obj); - error_cnt++; - return -EINVAL; - } - - *pobj = obj; - *prog_fd = bpf_program__fd(prog); - return 0; -} - static int bpf_find_map(const char *test, struct bpf_object *obj, const char *name) { @@ -130,8 +97,10 @@ static void test_pkt_access(void) int err, prog_fd; err = bpf_prog_load(file, BPF_PROG_TYPE_SCHED_CLS, &obj, &prog_fd); - if (err) + if (err) { + error_cnt++; return; + } err = bpf_prog_test_run(prog_fd, 100000, &pkt_v4, sizeof(pkt_v4), NULL, NULL, &retval, &duration); @@ -162,8 +131,10 @@ static void test_xdp(void) int err, prog_fd, map_fd; err = bpf_prog_load(file, BPF_PROG_TYPE_XDP, &obj, &prog_fd); - if (err) + if (err) { + error_cnt++; return; + } map_fd = bpf_find_map(__func__, obj, "vip2tnl"); if (map_fd < 0) @@ -223,8 +194,10 @@ static void test_l4lb(void) u32 *magic = (u32 *)buf; err = bpf_prog_load(file, BPF_PROG_TYPE_SCHED_CLS, &obj, &prog_fd); - if (err) + if (err) { + error_cnt++; return; + } map_fd = bpf_find_map(__func__, obj, "vip_map"); if (map_fd < 0) @@ -280,8 +253,10 @@ static void test_tcp_estats(void) err = bpf_prog_load(file, BPF_PROG_TYPE_TRACEPOINT, &obj, &prog_fd); CHECK(err, "", "err %d errno %d\n", err, errno); - if (err) + if (err) { + error_cnt++; return; + } bpf_object__close(obj); } @@ -336,6 +311,8 @@ static void test_bpf_obj_id(void) /* test_obj_id.o is a dumb prog. It should never fail * to load. */ + if (err) + error_cnt++; assert(!err); /* Check getting prog info */ @@ -496,8 +473,10 @@ static void test_pkt_md_access(void) int err, prog_fd; err = bpf_prog_load(file, BPF_PROG_TYPE_SCHED_CLS, &obj, &prog_fd); - if (err) + if (err) { + error_cnt++; return; + } err = bpf_prog_test_run(prog_fd, 10, &pkt_v4, sizeof(pkt_v4), NULL, NULL, &retval, &duration); |