Commit | Line | Data |
---|---|---|
ffb65f27 AS |
1 | /* |
2 | * Testsuite for eBPF maps | |
3 | * | |
4 | * Copyright (c) 2014 PLUMgrid, http://plumgrid.com | |
89b97607 | 5 | * Copyright (c) 2016 Facebook |
ffb65f27 AS |
6 | * |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of version 2 of the GNU General Public | |
9 | * License as published by the Free Software Foundation. | |
10 | */ | |
11 | #include <stdio.h> | |
12 | #include <unistd.h> | |
13 | #include <linux/bpf.h> | |
14 | #include <errno.h> | |
15 | #include <string.h> | |
16 | #include <assert.h> | |
17 | #include <sys/wait.h> | |
18 | #include <stdlib.h> | |
19 | #include "libbpf.h" | |
20 | ||
89b97607 AS |
21 | static int map_flags; |
22 | ||
ffb65f27 AS |
23 | /* sanity tests for map API */ |
24 | static void test_hashmap_sanity(int i, void *data) | |
25 | { | |
26 | long long key, next_key, value; | |
27 | int map_fd; | |
28 | ||
89b97607 AS |
29 | map_fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value), |
30 | 2, map_flags); | |
ffb65f27 AS |
31 | if (map_fd < 0) { |
32 | printf("failed to create hashmap '%s'\n", strerror(errno)); | |
33 | exit(1); | |
34 | } | |
35 | ||
36 | key = 1; | |
37 | value = 1234; | |
38 | /* insert key=1 element */ | |
39 | assert(bpf_update_elem(map_fd, &key, &value, BPF_ANY) == 0); | |
40 | ||
41 | value = 0; | |
42 | /* BPF_NOEXIST means: add new element if it doesn't exist */ | |
43 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | |
44 | /* key=1 already exists */ | |
45 | errno == EEXIST); | |
46 | ||
47 | assert(bpf_update_elem(map_fd, &key, &value, -1) == -1 && errno == EINVAL); | |
48 | ||
49 | /* check that key=1 can be found */ | |
50 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 1234); | |
51 | ||
52 | key = 2; | |
53 | /* check that key=2 is not found */ | |
54 | assert(bpf_lookup_elem(map_fd, &key, &value) == -1 && errno == ENOENT); | |
55 | ||
56 | /* BPF_EXIST means: update existing element */ | |
57 | assert(bpf_update_elem(map_fd, &key, &value, BPF_EXIST) == -1 && | |
58 | /* key=2 is not there */ | |
59 | errno == ENOENT); | |
60 | ||
61 | /* insert key=2 element */ | |
62 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == 0); | |
63 | ||
64 | /* key=1 and key=2 were inserted, check that key=0 cannot be inserted | |
65 | * due to max_entries limit | |
66 | */ | |
67 | key = 0; | |
68 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | |
69 | errno == E2BIG); | |
70 | ||
71 | /* check that key = 0 doesn't exist */ | |
72 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == ENOENT); | |
73 | ||
74 | /* iterate over two elements */ | |
75 | assert(bpf_get_next_key(map_fd, &key, &next_key) == 0 && | |
ba1a68bf | 76 | (next_key == 1 || next_key == 2)); |
ffb65f27 | 77 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == 0 && |
ba1a68bf | 78 | (next_key == 1 || next_key == 2)); |
ffb65f27 AS |
79 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == -1 && |
80 | errno == ENOENT); | |
81 | ||
82 | /* delete both elements */ | |
83 | key = 1; | |
84 | assert(bpf_delete_elem(map_fd, &key) == 0); | |
85 | key = 2; | |
86 | assert(bpf_delete_elem(map_fd, &key) == 0); | |
87 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == ENOENT); | |
88 | ||
89 | key = 0; | |
90 | /* check that map is empty */ | |
91 | assert(bpf_get_next_key(map_fd, &key, &next_key) == -1 && | |
92 | errno == ENOENT); | |
93 | close(map_fd); | |
94 | } | |
95 | ||
e1559671 MKL |
96 | /* sanity tests for percpu map API */ |
97 | static void test_percpu_hashmap_sanity(int task, void *data) | |
98 | { | |
99 | long long key, next_key; | |
100 | int expected_key_mask = 0; | |
101 | unsigned int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); | |
102 | long long value[nr_cpus]; | |
103 | int map_fd, i; | |
104 | ||
105 | map_fd = bpf_create_map(BPF_MAP_TYPE_PERCPU_HASH, sizeof(key), | |
89b97607 | 106 | sizeof(value[0]), 2, map_flags); |
e1559671 MKL |
107 | if (map_fd < 0) { |
108 | printf("failed to create hashmap '%s'\n", strerror(errno)); | |
109 | exit(1); | |
110 | } | |
111 | ||
112 | for (i = 0; i < nr_cpus; i++) | |
113 | value[i] = i + 100; | |
114 | key = 1; | |
115 | /* insert key=1 element */ | |
116 | assert(!(expected_key_mask & key)); | |
117 | assert(bpf_update_elem(map_fd, &key, value, BPF_ANY) == 0); | |
118 | expected_key_mask |= key; | |
119 | ||
120 | /* BPF_NOEXIST means: add new element if it doesn't exist */ | |
121 | assert(bpf_update_elem(map_fd, &key, value, BPF_NOEXIST) == -1 && | |
122 | /* key=1 already exists */ | |
123 | errno == EEXIST); | |
124 | ||
125 | /* -1 is an invalid flag */ | |
126 | assert(bpf_update_elem(map_fd, &key, value, -1) == -1 && | |
127 | errno == EINVAL); | |
128 | ||
129 | /* check that key=1 can be found. value could be 0 if the lookup | |
130 | * was run from a different cpu. | |
131 | */ | |
132 | value[0] = 1; | |
133 | assert(bpf_lookup_elem(map_fd, &key, value) == 0 && value[0] == 100); | |
134 | ||
135 | key = 2; | |
136 | /* check that key=2 is not found */ | |
137 | assert(bpf_lookup_elem(map_fd, &key, value) == -1 && errno == ENOENT); | |
138 | ||
139 | /* BPF_EXIST means: update existing element */ | |
140 | assert(bpf_update_elem(map_fd, &key, value, BPF_EXIST) == -1 && | |
141 | /* key=2 is not there */ | |
142 | errno == ENOENT); | |
143 | ||
144 | /* insert key=2 element */ | |
145 | assert(!(expected_key_mask & key)); | |
146 | assert(bpf_update_elem(map_fd, &key, value, BPF_NOEXIST) == 0); | |
147 | expected_key_mask |= key; | |
148 | ||
149 | /* key=1 and key=2 were inserted, check that key=0 cannot be inserted | |
150 | * due to max_entries limit | |
151 | */ | |
152 | key = 0; | |
153 | assert(bpf_update_elem(map_fd, &key, value, BPF_NOEXIST) == -1 && | |
154 | errno == E2BIG); | |
155 | ||
156 | /* check that key = 0 doesn't exist */ | |
157 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == ENOENT); | |
158 | ||
159 | /* iterate over two elements */ | |
160 | while (!bpf_get_next_key(map_fd, &key, &next_key)) { | |
161 | assert((expected_key_mask & next_key) == next_key); | |
162 | expected_key_mask &= ~next_key; | |
163 | ||
164 | assert(bpf_lookup_elem(map_fd, &next_key, value) == 0); | |
165 | for (i = 0; i < nr_cpus; i++) | |
166 | assert(value[i] == i + 100); | |
167 | ||
168 | key = next_key; | |
169 | } | |
170 | assert(errno == ENOENT); | |
171 | ||
172 | /* Update with BPF_EXIST */ | |
173 | key = 1; | |
174 | assert(bpf_update_elem(map_fd, &key, value, BPF_EXIST) == 0); | |
175 | ||
176 | /* delete both elements */ | |
177 | key = 1; | |
178 | assert(bpf_delete_elem(map_fd, &key) == 0); | |
179 | key = 2; | |
180 | assert(bpf_delete_elem(map_fd, &key) == 0); | |
181 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == ENOENT); | |
182 | ||
183 | key = 0; | |
184 | /* check that map is empty */ | |
185 | assert(bpf_get_next_key(map_fd, &key, &next_key) == -1 && | |
186 | errno == ENOENT); | |
187 | close(map_fd); | |
188 | } | |
189 | ||
ffb65f27 AS |
190 | static void test_arraymap_sanity(int i, void *data) |
191 | { | |
192 | int key, next_key, map_fd; | |
193 | long long value; | |
194 | ||
89b97607 AS |
195 | map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), |
196 | 2, 0); | |
ffb65f27 AS |
197 | if (map_fd < 0) { |
198 | printf("failed to create arraymap '%s'\n", strerror(errno)); | |
199 | exit(1); | |
200 | } | |
201 | ||
202 | key = 1; | |
203 | value = 1234; | |
204 | /* insert key=1 element */ | |
205 | assert(bpf_update_elem(map_fd, &key, &value, BPF_ANY) == 0); | |
206 | ||
207 | value = 0; | |
208 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | |
209 | errno == EEXIST); | |
210 | ||
211 | /* check that key=1 can be found */ | |
212 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 1234); | |
213 | ||
214 | key = 0; | |
215 | /* check that key=0 is also found and zero initialized */ | |
216 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 0); | |
217 | ||
218 | ||
219 | /* key=0 and key=1 were inserted, check that key=2 cannot be inserted | |
220 | * due to max_entries limit | |
221 | */ | |
222 | key = 2; | |
223 | assert(bpf_update_elem(map_fd, &key, &value, BPF_EXIST) == -1 && | |
224 | errno == E2BIG); | |
225 | ||
226 | /* check that key = 2 doesn't exist */ | |
227 | assert(bpf_lookup_elem(map_fd, &key, &value) == -1 && errno == ENOENT); | |
228 | ||
229 | /* iterate over two elements */ | |
230 | assert(bpf_get_next_key(map_fd, &key, &next_key) == 0 && | |
231 | next_key == 0); | |
232 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == 0 && | |
233 | next_key == 1); | |
234 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == -1 && | |
235 | errno == ENOENT); | |
236 | ||
237 | /* delete shouldn't succeed */ | |
238 | key = 1; | |
239 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == EINVAL); | |
240 | ||
241 | close(map_fd); | |
242 | } | |
243 | ||
df570f57 | 244 | static void test_percpu_arraymap_many_keys(void) |
245 | { | |
246 | unsigned nr_cpus = sysconf(_SC_NPROCESSORS_CONF); | |
247 | unsigned nr_keys = 20000; | |
248 | long values[nr_cpus]; | |
249 | int key, map_fd, i; | |
250 | ||
251 | map_fd = bpf_create_map(BPF_MAP_TYPE_PERCPU_ARRAY, sizeof(key), | |
89b97607 | 252 | sizeof(values[0]), nr_keys, 0); |
df570f57 | 253 | if (map_fd < 0) { |
254 | printf("failed to create per-cpu arraymap '%s'\n", | |
255 | strerror(errno)); | |
256 | exit(1); | |
257 | } | |
258 | ||
259 | for (i = 0; i < nr_cpus; i++) | |
260 | values[i] = i + 10; | |
261 | ||
262 | for (key = 0; key < nr_keys; key++) | |
263 | assert(bpf_update_elem(map_fd, &key, values, BPF_ANY) == 0); | |
264 | ||
265 | for (key = 0; key < nr_keys; key++) { | |
266 | for (i = 0; i < nr_cpus; i++) | |
267 | values[i] = 0; | |
268 | assert(bpf_lookup_elem(map_fd, &key, values) == 0); | |
269 | for (i = 0; i < nr_cpus; i++) | |
270 | assert(values[i] == i + 10); | |
271 | } | |
272 | ||
273 | close(map_fd); | |
274 | } | |
275 | ||
276 | static void test_percpu_arraymap_sanity(int i, void *data) | |
277 | { | |
278 | unsigned nr_cpus = sysconf(_SC_NPROCESSORS_CONF); | |
279 | long values[nr_cpus]; | |
280 | int key, next_key, map_fd; | |
281 | ||
282 | map_fd = bpf_create_map(BPF_MAP_TYPE_PERCPU_ARRAY, sizeof(key), | |
89b97607 | 283 | sizeof(values[0]), 2, 0); |
df570f57 | 284 | if (map_fd < 0) { |
285 | printf("failed to create arraymap '%s'\n", strerror(errno)); | |
286 | exit(1); | |
287 | } | |
288 | ||
289 | for (i = 0; i < nr_cpus; i++) | |
290 | values[i] = i + 100; | |
291 | ||
292 | key = 1; | |
293 | /* insert key=1 element */ | |
294 | assert(bpf_update_elem(map_fd, &key, values, BPF_ANY) == 0); | |
295 | ||
296 | values[0] = 0; | |
297 | assert(bpf_update_elem(map_fd, &key, values, BPF_NOEXIST) == -1 && | |
298 | errno == EEXIST); | |
299 | ||
300 | /* check that key=1 can be found */ | |
301 | assert(bpf_lookup_elem(map_fd, &key, values) == 0 && values[0] == 100); | |
302 | ||
303 | key = 0; | |
304 | /* check that key=0 is also found and zero initialized */ | |
305 | assert(bpf_lookup_elem(map_fd, &key, values) == 0 && | |
306 | values[0] == 0 && values[nr_cpus - 1] == 0); | |
307 | ||
308 | ||
309 | /* check that key=2 cannot be inserted due to max_entries limit */ | |
310 | key = 2; | |
311 | assert(bpf_update_elem(map_fd, &key, values, BPF_EXIST) == -1 && | |
312 | errno == E2BIG); | |
313 | ||
314 | /* check that key = 2 doesn't exist */ | |
315 | assert(bpf_lookup_elem(map_fd, &key, values) == -1 && errno == ENOENT); | |
316 | ||
317 | /* iterate over two elements */ | |
318 | assert(bpf_get_next_key(map_fd, &key, &next_key) == 0 && | |
319 | next_key == 0); | |
320 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == 0 && | |
321 | next_key == 1); | |
322 | assert(bpf_get_next_key(map_fd, &next_key, &next_key) == -1 && | |
323 | errno == ENOENT); | |
324 | ||
325 | /* delete shouldn't succeed */ | |
326 | key = 1; | |
327 | assert(bpf_delete_elem(map_fd, &key) == -1 && errno == EINVAL); | |
328 | ||
329 | close(map_fd); | |
330 | } | |
331 | ||
ffb65f27 AS |
332 | #define MAP_SIZE (32 * 1024) |
333 | static void test_map_large(void) | |
334 | { | |
335 | struct bigkey { | |
336 | int a; | |
337 | char b[116]; | |
338 | long long c; | |
339 | } key; | |
340 | int map_fd, i, value; | |
341 | ||
342 | /* allocate 4Mbyte of memory */ | |
343 | map_fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value), | |
89b97607 | 344 | MAP_SIZE, map_flags); |
ffb65f27 AS |
345 | if (map_fd < 0) { |
346 | printf("failed to create large map '%s'\n", strerror(errno)); | |
347 | exit(1); | |
348 | } | |
349 | ||
350 | for (i = 0; i < MAP_SIZE; i++) { | |
351 | key = (struct bigkey) {.c = i}; | |
352 | value = i; | |
353 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == 0); | |
354 | } | |
355 | key.c = -1; | |
356 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | |
357 | errno == E2BIG); | |
358 | ||
359 | /* iterate through all elements */ | |
360 | for (i = 0; i < MAP_SIZE; i++) | |
361 | assert(bpf_get_next_key(map_fd, &key, &key) == 0); | |
362 | assert(bpf_get_next_key(map_fd, &key, &key) == -1 && errno == ENOENT); | |
363 | ||
364 | key.c = 0; | |
365 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && value == 0); | |
366 | key.a = 1; | |
367 | assert(bpf_lookup_elem(map_fd, &key, &value) == -1 && errno == ENOENT); | |
368 | ||
369 | close(map_fd); | |
370 | } | |
371 | ||
372 | /* fork N children and wait for them to complete */ | |
373 | static void run_parallel(int tasks, void (*fn)(int i, void *data), void *data) | |
374 | { | |
375 | pid_t pid[tasks]; | |
376 | int i; | |
377 | ||
378 | for (i = 0; i < tasks; i++) { | |
379 | pid[i] = fork(); | |
380 | if (pid[i] == 0) { | |
381 | fn(i, data); | |
382 | exit(0); | |
383 | } else if (pid[i] == -1) { | |
384 | printf("couldn't spawn #%d process\n", i); | |
385 | exit(1); | |
386 | } | |
387 | } | |
388 | for (i = 0; i < tasks; i++) { | |
389 | int status; | |
390 | ||
391 | assert(waitpid(pid[i], &status, 0) == pid[i]); | |
392 | assert(status == 0); | |
393 | } | |
394 | } | |
395 | ||
396 | static void test_map_stress(void) | |
397 | { | |
398 | run_parallel(100, test_hashmap_sanity, NULL); | |
e1559671 | 399 | run_parallel(100, test_percpu_hashmap_sanity, NULL); |
ffb65f27 | 400 | run_parallel(100, test_arraymap_sanity, NULL); |
df570f57 | 401 | run_parallel(100, test_percpu_arraymap_sanity, NULL); |
ffb65f27 AS |
402 | } |
403 | ||
404 | #define TASKS 1024 | |
405 | #define DO_UPDATE 1 | |
406 | #define DO_DELETE 0 | |
407 | static void do_work(int fn, void *data) | |
408 | { | |
409 | int map_fd = ((int *)data)[0]; | |
410 | int do_update = ((int *)data)[1]; | |
411 | int i; | |
412 | int key, value; | |
413 | ||
414 | for (i = fn; i < MAP_SIZE; i += TASKS) { | |
415 | key = value = i; | |
416 | if (do_update) | |
417 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == 0); | |
418 | else | |
419 | assert(bpf_delete_elem(map_fd, &key) == 0); | |
420 | } | |
421 | } | |
422 | ||
423 | static void test_map_parallel(void) | |
424 | { | |
425 | int i, map_fd, key = 0, value = 0; | |
426 | int data[2]; | |
427 | ||
428 | map_fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value), | |
89b97607 | 429 | MAP_SIZE, map_flags); |
ffb65f27 AS |
430 | if (map_fd < 0) { |
431 | printf("failed to create map for parallel test '%s'\n", | |
432 | strerror(errno)); | |
433 | exit(1); | |
434 | } | |
435 | ||
436 | data[0] = map_fd; | |
437 | data[1] = DO_UPDATE; | |
438 | /* use the same map_fd in children to add elements to this map | |
439 | * child_0 adds key=0, key=1024, key=2048, ... | |
440 | * child_1 adds key=1, key=1025, key=2049, ... | |
441 | * child_1023 adds key=1023, ... | |
442 | */ | |
443 | run_parallel(TASKS, do_work, data); | |
444 | ||
445 | /* check that key=0 is already there */ | |
446 | assert(bpf_update_elem(map_fd, &key, &value, BPF_NOEXIST) == -1 && | |
447 | errno == EEXIST); | |
448 | ||
449 | /* check that all elements were inserted */ | |
450 | key = -1; | |
451 | for (i = 0; i < MAP_SIZE; i++) | |
452 | assert(bpf_get_next_key(map_fd, &key, &key) == 0); | |
453 | assert(bpf_get_next_key(map_fd, &key, &key) == -1 && errno == ENOENT); | |
454 | ||
455 | /* another check for all elements */ | |
456 | for (i = 0; i < MAP_SIZE; i++) { | |
457 | key = MAP_SIZE - i - 1; | |
458 | assert(bpf_lookup_elem(map_fd, &key, &value) == 0 && | |
459 | value == key); | |
460 | } | |
461 | ||
462 | /* now let's delete all elemenets in parallel */ | |
463 | data[1] = DO_DELETE; | |
464 | run_parallel(TASKS, do_work, data); | |
465 | ||
466 | /* nothing should be left */ | |
467 | key = -1; | |
468 | assert(bpf_get_next_key(map_fd, &key, &key) == -1 && errno == ENOENT); | |
469 | } | |
470 | ||
c3f85cff | 471 | static void run_all_tests(void) |
ffb65f27 AS |
472 | { |
473 | test_hashmap_sanity(0, NULL); | |
e1559671 | 474 | test_percpu_hashmap_sanity(0, NULL); |
ffb65f27 | 475 | test_arraymap_sanity(0, NULL); |
df570f57 | 476 | test_percpu_arraymap_sanity(0, NULL); |
477 | test_percpu_arraymap_many_keys(); | |
478 | ||
ffb65f27 AS |
479 | test_map_large(); |
480 | test_map_parallel(); | |
481 | test_map_stress(); | |
c3f85cff AS |
482 | } |
483 | ||
484 | int main(void) | |
485 | { | |
486 | map_flags = 0; | |
487 | run_all_tests(); | |
488 | map_flags = BPF_F_NO_PREALLOC; | |
489 | run_all_tests(); | |
ffb65f27 AS |
490 | printf("test_maps: OK\n"); |
491 | return 0; | |
492 | } |