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