diff options
author | Alexei Starovoitov <ast@plumgrid.com> | 2014-11-13 20:36:48 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-11-18 13:43:59 -0500 |
commit | ffb65f27a15583379567b6a59a9758163b7f5750 (patch) | |
tree | 055058bbf98518f31a3aebf6f1ac425f70b1f71a /samples | |
parent | a1854d6ac0008518bfc45e791172ad250999c2a2 (diff) |
bpf: add a testsuite for eBPF maps
. check error conditions and sanity of hash and array map APIs
. check large maps (that kernel gracefully switches to vmalloc from kmalloc)
. check multi-process parallel access and stress test
Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'samples')
-rw-r--r-- | samples/bpf/Makefile | 3 | ||||
-rw-r--r-- | samples/bpf/libbpf.c | 3 | ||||
-rw-r--r-- | samples/bpf/libbpf.h | 2 | ||||
-rw-r--r-- | samples/bpf/test_maps.c | 291 |
4 files changed, 296 insertions, 3 deletions
diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile index 634391797856..0718d9ce4619 100644 --- a/samples/bpf/Makefile +++ b/samples/bpf/Makefile | |||
@@ -2,9 +2,10 @@ | |||
2 | obj- := dummy.o | 2 | obj- := dummy.o |
3 | 3 | ||
4 | # List of programs to build | 4 | # List of programs to build |
5 | hostprogs-y := test_verifier | 5 | hostprogs-y := test_verifier test_maps |
6 | 6 | ||
7 | test_verifier-objs := test_verifier.o libbpf.o | 7 | test_verifier-objs := test_verifier.o libbpf.o |
8 | test_maps-objs := test_maps.o libbpf.o | ||
8 | 9 | ||
9 | # Tell kbuild to always build the programs | 10 | # Tell kbuild to always build the programs |
10 | always := $(hostprogs-y) | 11 | always := $(hostprogs-y) |
diff --git a/samples/bpf/libbpf.c b/samples/bpf/libbpf.c index ff6504420738..17bb520eb57f 100644 --- a/samples/bpf/libbpf.c +++ b/samples/bpf/libbpf.c | |||
@@ -27,12 +27,13 @@ int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, | |||
27 | return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); | 27 | return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); |
28 | } | 28 | } |
29 | 29 | ||
30 | int bpf_update_elem(int fd, void *key, void *value) | 30 | int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags) |
31 | { | 31 | { |
32 | union bpf_attr attr = { | 32 | union bpf_attr attr = { |
33 | .map_fd = fd, | 33 | .map_fd = fd, |
34 | .key = ptr_to_u64(key), | 34 | .key = ptr_to_u64(key), |
35 | .value = ptr_to_u64(value), | 35 | .value = ptr_to_u64(value), |
36 | .flags = flags, | ||
36 | }; | 37 | }; |
37 | 38 | ||
38 | return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); | 39 | return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); |
diff --git a/samples/bpf/libbpf.h b/samples/bpf/libbpf.h index 8a31babeca5d..f8678e5f48bf 100644 --- a/samples/bpf/libbpf.h +++ b/samples/bpf/libbpf.h | |||
@@ -6,7 +6,7 @@ struct bpf_insn; | |||
6 | 6 | ||
7 | int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, | 7 | int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, |
8 | int max_entries); | 8 | int max_entries); |
9 | int bpf_update_elem(int fd, void *key, void *value); | 9 | int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags); |
10 | int bpf_lookup_elem(int fd, void *key, void *value); | 10 | int bpf_lookup_elem(int fd, void *key, void *value); |
11 | int bpf_delete_elem(int fd, void *key); | 11 | int bpf_delete_elem(int fd, void *key); |
12 | int bpf_get_next_key(int fd, void *key, void *next_key); | 12 | int bpf_get_next_key(int fd, void *key, void *next_key); |
diff --git a/samples/bpf/test_maps.c b/samples/bpf/test_maps.c new file mode 100644 index 000000000000..e286b42307f3 --- /dev/null +++ b/samples/bpf/test_maps.c | |||
@@ -0,0 +1,291 @@ | |||
1 | /* | ||
2 | * Testsuite for eBPF maps | ||
3 | * | ||
4 | * Copyright (c) 2014 PLUMgrid, http://plumgrid.com | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of version 2 of the GNU General Public | ||
8 | * License as published by the Free Software Foundation. | ||
9 | */ | ||
10 | #include <stdio.h> | ||
11 | #include <unistd.h> | ||
12 | #include <linux/bpf.h> | ||
13 | #include <errno.h> | ||
14 | #include <string.h> | ||
15 | #include <assert.h> | ||
16 | #include <sys/wait.h> | ||
17 | #include <stdlib.h> | ||
18 | #include "libbpf.h" | ||
19 | |||
20 | /* sanity tests for map API */ | ||
21 | static void test_hashmap_sanity(int i, void *data) | ||
22 | { | ||
23 | long long key, next_key, value; | ||
24 | int map_fd; | ||
25 | |||
26 | map_fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value), 2); | ||
27 | if (map_fd < 0) { | ||
28 | printf("failed to create hashmap '%s'\n", strerror(errno)); | ||
29 | exit(1); | ||
30 | } | ||
31 | |||
32 | key = 1; | ||
33 | value = 1234; | ||
34 | /* insert key=1 element */ | ||
35 | assert(bpf_update_elem(map_fd, &key, &value, BPF_ANY) == 0); | ||
36 | |||
37 | value = 0; | ||
38 | /* BPF_NOEXIST means: add new element if it doesn't exist */ | ||
39 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | ||
40 | /* key=1 already exists */ | ||
41 | errno == EEXIST); | ||
42 | |||
43 | assert(bpf_update_elem(map_fd, &key, &value, -1) == -1 && errno == EINVAL); | ||
44 | |||
45 | /* check that key=1 can be found */ | ||
46 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 1234); | ||
47 | |||
48 | key = 2; | ||
49 | /* check that key=2 is not found */ | ||
50 | assert(bpf_lookup_elem(map_fd, &key, &value) == -1 && errno == ENOENT); | ||
51 | |||
52 | /* BPF_EXIST means: update existing element */ | ||
53 | assert(bpf_update_elem(map_fd, &key, &value, BPF_EXIST) == -1 && | ||
54 | /* key=2 is not there */ | ||
55 | errno == ENOENT); | ||
56 | |||
57 | /* insert key=2 element */ | ||
58 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == 0); | ||
59 | |||
60 | /* key=1 and key=2 were inserted, check that key=0 cannot be inserted | ||
61 | * due to max_entries limit | ||
62 | */ | ||
63 | key = 0; | ||
64 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | ||
65 | errno == E2BIG); | ||
66 | |||
67 | /* check that key = 0 doesn't exist */ | ||
68 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == ENOENT); | ||
69 | |||
70 | /* iterate over two elements */ | ||
71 | assert(bpf_get_next_key(map_fd, &key, &next_key) == 0 && | ||
72 | next_key == 2); | ||
73 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == 0 && | ||
74 | next_key == 1); | ||
75 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == -1 && | ||
76 | errno == ENOENT); | ||
77 | |||
78 | /* delete both elements */ | ||
79 | key = 1; | ||
80 | assert(bpf_delete_elem(map_fd, &key) == 0); | ||
81 | key = 2; | ||
82 | assert(bpf_delete_elem(map_fd, &key) == 0); | ||
83 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == ENOENT); | ||
84 | |||
85 | key = 0; | ||
86 | /* check that map is empty */ | ||
87 | assert(bpf_get_next_key(map_fd, &key, &next_key) == -1 && | ||
88 | errno == ENOENT); | ||
89 | close(map_fd); | ||
90 | } | ||
91 | |||
92 | static void test_arraymap_sanity(int i, void *data) | ||
93 | { | ||
94 | int key, next_key, map_fd; | ||
95 | long long value; | ||
96 | |||
97 | map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2); | ||
98 | if (map_fd < 0) { | ||
99 | printf("failed to create arraymap '%s'\n", strerror(errno)); | ||
100 | exit(1); | ||
101 | } | ||
102 | |||
103 | key = 1; | ||
104 | value = 1234; | ||
105 | /* insert key=1 element */ | ||
106 | assert(bpf_update_elem(map_fd, &key, &value, BPF_ANY) == 0); | ||
107 | |||
108 | value = 0; | ||
109 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | ||
110 | errno == EEXIST); | ||
111 | |||
112 | /* check that key=1 can be found */ | ||
113 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 1234); | ||
114 | |||
115 | key = 0; | ||
116 | /* check that key=0 is also found and zero initialized */ | ||
117 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 0); | ||
118 | |||
119 | |||
120 | /* key=0 and key=1 were inserted, check that key=2 cannot be inserted | ||
121 | * due to max_entries limit | ||
122 | */ | ||
123 | key = 2; | ||
124 | assert(bpf_update_elem(map_fd, &key, &value, BPF_EXIST) == -1 && | ||
125 | errno == E2BIG); | ||
126 | |||
127 | /* check that key = 2 doesn't exist */ | ||
128 | assert(bpf_lookup_elem(map_fd, &key, &value) == -1 && errno == ENOENT); | ||
129 | |||
130 | /* iterate over two elements */ | ||
131 | assert(bpf_get_next_key(map_fd, &key, &next_key) == 0 && | ||
132 | next_key == 0); | ||
133 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == 0 && | ||
134 | next_key == 1); | ||
135 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == -1 && | ||
136 | errno == ENOENT); | ||
137 | |||
138 | /* delete shouldn't succeed */ | ||
139 | key = 1; | ||
140 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == EINVAL); | ||
141 | |||
142 | close(map_fd); | ||
143 | } | ||
144 | |||
145 | #define MAP_SIZE (32 * 1024) | ||
146 | static void test_map_large(void) | ||
147 | { | ||
148 | struct bigkey { | ||
149 | int a; | ||
150 | char b[116]; | ||
151 | long long c; | ||
152 | } key; | ||
153 | int map_fd, i, value; | ||
154 | |||
155 | /* allocate 4Mbyte of memory */ | ||
156 | map_fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value), | ||
157 | MAP_SIZE); | ||
158 | if (map_fd < 0) { | ||
159 | printf("failed to create large map '%s'\n", strerror(errno)); | ||
160 | exit(1); | ||
161 | } | ||
162 | |||
163 | for (i = 0; i < MAP_SIZE; i++) { | ||
164 | key = (struct bigkey) {.c = i}; | ||
165 | value = i; | ||
166 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == 0); | ||
167 | } | ||
168 | key.c = -1; | ||
169 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | ||
170 | errno == E2BIG); | ||
171 | |||
172 | /* iterate through all elements */ | ||
173 | for (i = 0; i < MAP_SIZE; i++) | ||
174 | assert(bpf_get_next_key(map_fd, &key, &key) == 0); | ||
175 | assert(bpf_get_next_key(map_fd, &key, &key) == -1 && errno == ENOENT); | ||
176 | |||
177 | key.c = 0; | ||
178 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 0); | ||
179 | key.a = 1; | ||
180 | assert(bpf_lookup_elem(map_fd, &key, &value) == -1 && errno == ENOENT); | ||
181 | |||
182 | close(map_fd); | ||
183 | } | ||
184 | |||
185 | /* fork N children and wait for them to complete */ | ||
186 | static void run_parallel(int tasks, void (*fn)(int i, void *data), void *data) | ||
187 | { | ||
188 | pid_t pid[tasks]; | ||
189 | int i; | ||
190 | |||
191 | for (i = 0; i < tasks; i++) { | ||
192 | pid[i] = fork(); | ||
193 | if (pid[i] == 0) { | ||
194 | fn(i, data); | ||
195 | exit(0); | ||
196 | } else if (pid[i] == -1) { | ||
197 | printf("couldn't spawn #%d process\n", i); | ||
198 | exit(1); | ||
199 | } | ||
200 | } | ||
201 | for (i = 0; i < tasks; i++) { | ||
202 | int status; | ||
203 | |||
204 | assert(waitpid(pid[i], &status, 0) == pid[i]); | ||
205 | assert(status == 0); | ||
206 | } | ||
207 | } | ||
208 | |||
209 | static void test_map_stress(void) | ||
210 | { | ||
211 | run_parallel(100, test_hashmap_sanity, NULL); | ||
212 | run_parallel(100, test_arraymap_sanity, NULL); | ||
213 | } | ||
214 | |||
215 | #define TASKS 1024 | ||
216 | #define DO_UPDATE 1 | ||
217 | #define DO_DELETE 0 | ||
218 | static void do_work(int fn, void *data) | ||
219 | { | ||
220 | int map_fd = ((int *)data)[0]; | ||
221 | int do_update = ((int *)data)[1]; | ||
222 | int i; | ||
223 | int key, value; | ||
224 | |||
225 | for (i = fn; i < MAP_SIZE; i += TASKS) { | ||
226 | key = value = i; | ||
227 | if (do_update) | ||
228 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == 0); | ||
229 | else | ||
230 | assert(bpf_delete_elem(map_fd, &key) == 0); | ||
231 | } | ||
232 | } | ||
233 | |||
234 | static void test_map_parallel(void) | ||
235 | { | ||
236 | int i, map_fd, key = 0, value = 0; | ||
237 | int data[2]; | ||
238 | |||
239 | map_fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value), | ||
240 | MAP_SIZE); | ||
241 | if (map_fd < 0) { | ||
242 | printf("failed to create map for parallel test '%s'\n", | ||
243 | strerror(errno)); | ||
244 | exit(1); | ||
245 | } | ||
246 | |||
247 | data[0] = map_fd; | ||
248 | data[1] = DO_UPDATE; | ||
249 | /* use the same map_fd in children to add elements to this map | ||
250 | * child_0 adds key=0, key=1024, key=2048, ... | ||
251 | * child_1 adds key=1, key=1025, key=2049, ... | ||
252 | * child_1023 adds key=1023, ... | ||
253 | */ | ||
254 | run_parallel(TASKS, do_work, data); | ||
255 | |||
256 | /* check that key=0 is already there */ | ||
257 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | ||
258 | errno == EEXIST); | ||
259 | |||
260 | /* check that all elements were inserted */ | ||
261 | key = -1; | ||
262 | for (i = 0; i < MAP_SIZE; i++) | ||
263 | assert(bpf_get_next_key(map_fd, &key, &key) == 0); | ||
264 | assert(bpf_get_next_key(map_fd, &key, &key) == -1 && errno == ENOENT); | ||
265 | |||
266 | /* another check for all elements */ | ||
267 | for (i = 0; i < MAP_SIZE; i++) { | ||
268 | key = MAP_SIZE - i - 1; | ||
269 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && | ||
270 | value == key); | ||
271 | } | ||
272 | |||
273 | /* now let's delete all elemenets in parallel */ | ||
274 | data[1] = DO_DELETE; | ||
275 | run_parallel(TASKS, do_work, data); | ||
276 | |||
277 | /* nothing should be left */ | ||
278 | key = -1; | ||
279 | assert(bpf_get_next_key(map_fd, &key, &key) == -1 && errno == ENOENT); | ||
280 | } | ||
281 | |||
282 | int main(void) | ||
283 | { | ||
284 | test_hashmap_sanity(0, NULL); | ||
285 | test_arraymap_sanity(0, NULL); | ||
286 | test_map_large(); | ||
287 | test_map_parallel(); | ||
288 | test_map_stress(); | ||
289 | printf("test_maps: OK\n"); | ||
290 | return 0; | ||
291 | } | ||