Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
tests.h
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <test_maps.h>
static int nr_cpus;
static void map_batch_update(int map_fd, __u32 max_entries, int *keys,
__s64 *values, bool is_pcpu)
{
int i, j, err;
int cpu_offset = 0;
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
for (i = 0; i < max_entries; i++) {
keys[i] = i;
if (is_pcpu) {
cpu_offset = i * nr_cpus;
for (j = 0; j < nr_cpus; j++)
(values + cpu_offset)[j] = i + 1 + j;
} else {
values[i] = i + 1;
}
}
err = bpf_map_update_batch(map_fd, keys, values, &max_entries, &opts);
CHECK(err, "bpf_map_update_batch()", "error:%s\n", strerror(errno));
}
static void map_batch_verify(int *visited, __u32 max_entries, int *keys,
__s64 *values, bool is_pcpu)
{
int i, j;
int cpu_offset = 0;
memset(visited, 0, max_entries * sizeof(*visited));
for (i = 0; i < max_entries; i++) {
if (is_pcpu) {
cpu_offset = i * nr_cpus;
for (j = 0; j < nr_cpus; j++) {
__s64 value = (values + cpu_offset)[j];
CHECK(keys[i] + j + 1 != value,
"key/value checking",
"error: i %d j %d key %d value %lld\n", i,
j, keys[i], value);
}
} else {
CHECK(keys[i] + 1 != values[i], "key/value checking",
"error: i %d key %d value %lld\n", i, keys[i],
values[i]);
}
visited[i] = 1;
}
for (i = 0; i < max_entries; i++) {
CHECK(visited[i] != 1, "visited checking",
"error: keys array at index %d missing\n", i);
}
}
static void __test_map_lookup_and_update_batch(bool is_pcpu)
{
int map_fd, *keys, *visited;
__u32 count, total, total_success;
const __u32 max_entries = 10;
__u64 batch = 0;
int err, step, value_size;
void *values;
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
map_fd = bpf_map_create(is_pcpu ? BPF_MAP_TYPE_PERCPU_ARRAY : BPF_MAP_TYPE_ARRAY,
"array_map", sizeof(int), sizeof(__s64), max_entries, NULL);
CHECK(map_fd == -1,
"bpf_map_create()", "error:%s\n", strerror(errno));
value_size = sizeof(__s64);
if (is_pcpu)
value_size *= nr_cpus;
keys = calloc(max_entries, sizeof(*keys));
values = calloc(max_entries, value_size);
visited = calloc(max_entries, sizeof(*visited));
CHECK(!keys || !values || !visited, "malloc()", "error:%s\n",
strerror(errno));
/* test 1: lookup in a loop with various steps. */
total_success = 0;
for (step = 1; step < max_entries; step++) {
map_batch_update(map_fd, max_entries, keys, values, is_pcpu);
map_batch_verify(visited, max_entries, keys, values, is_pcpu);
memset(keys, 0, max_entries * sizeof(*keys));
memset(values, 0, max_entries * value_size);
batch = 0;
total = 0;
/* iteratively lookup/delete elements with 'step'
* elements each.
*/
count = step;
while (true) {
err = bpf_map_lookup_batch(map_fd,
total ? &batch : NULL,
&batch, keys + total,
values + total * value_size,
&count, &opts);
CHECK((err && errno != ENOENT), "lookup with steps",
"error: %s\n", strerror(errno));
total += count;
if (err)
break;
}
CHECK(total != max_entries, "lookup with steps",
"total = %u, max_entries = %u\n", total, max_entries);
map_batch_verify(visited, max_entries, keys, values, is_pcpu);
total_success++;
}
CHECK(total_success == 0, "check total_success",
"unexpected failure\n");
free(keys);
free(values);
free(visited);
close(map_fd);
}
static void array_map_batch_ops(void)
{
__test_map_lookup_and_update_batch(false);
printf("test_%s:PASS\n", __func__);
}
static void array_percpu_map_batch_ops(void)
{
__test_map_lookup_and_update_batch(true);
printf("test_%s:PASS\n", __func__);
}
void test_array_map_batch_ops(void)
{
nr_cpus = libbpf_num_possible_cpus();
CHECK(nr_cpus < 0, "nr_cpus checking",
"error: get possible cpus failed");
array_map_batch_ops();
array_percpu_map_batch_ops();
}
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019 Facebook */
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf_util.h>
#include <test_maps.h>
static void map_batch_update(int map_fd, __u32 max_entries, int *keys,
void *values, bool is_pcpu)
{
typedef BPF_DECLARE_PERCPU(int, value);
value *v = NULL;
int i, j, err;
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
if (is_pcpu)
v = (value *)values;
for (i = 0; i < max_entries; i++) {
keys[i] = i + 1;
if (is_pcpu)
for (j = 0; j < bpf_num_possible_cpus(); j++)
bpf_percpu(v[i], j) = i + 2 + j;
else
((int *)values)[i] = i + 2;
}
err = bpf_map_update_batch(map_fd, keys, values, &max_entries, &opts);
CHECK(err, "bpf_map_update_batch()", "error:%s\n", strerror(errno));
}
static void map_batch_verify(int *visited, __u32 max_entries,
int *keys, void *values, bool is_pcpu)
{
typedef BPF_DECLARE_PERCPU(int, value);
value *v = NULL;
int i, j;
if (is_pcpu)
v = (value *)values;
memset(visited, 0, max_entries * sizeof(*visited));
for (i = 0; i < max_entries; i++) {
if (is_pcpu) {
for (j = 0; j < bpf_num_possible_cpus(); j++) {
CHECK(keys[i] + 1 + j != bpf_percpu(v[i], j),
"key/value checking",
"error: i %d j %d key %d value %d\n",
i, j, keys[i], bpf_percpu(v[i], j));
}
} else {
CHECK(keys[i] + 1 != ((int *)values)[i],
"key/value checking",
"error: i %d key %d value %d\n", i, keys[i],
((int *)values)[i]);
}
visited[i] = 1;
}
for (i = 0; i < max_entries; i++) {
CHECK(visited[i] != 1, "visited checking",
"error: keys array at index %d missing\n", i);
}
}
void __test_map_lookup_and_delete_batch(bool is_pcpu)
{
__u32 batch, count, total, total_success;
typedef BPF_DECLARE_PERCPU(int, value);
int map_fd, *keys, *visited, key;
const __u32 max_entries = 10;
value pcpu_values[max_entries];
int err, step, value_size;
bool nospace_err;
void *values;
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
map_fd = bpf_map_create(is_pcpu ? BPF_MAP_TYPE_PERCPU_HASH : BPF_MAP_TYPE_HASH,
"hash_map", sizeof(int), sizeof(int), max_entries, NULL);
CHECK(map_fd == -1,
"bpf_map_create()", "error:%s\n", strerror(errno));
value_size = is_pcpu ? sizeof(value) : sizeof(int);
keys = malloc(max_entries * sizeof(int));
if (is_pcpu)
values = pcpu_values;
else
values = malloc(max_entries * sizeof(int));
visited = malloc(max_entries * sizeof(int));
CHECK(!keys || !values || !visited, "malloc()",
"error:%s\n", strerror(errno));
/* test 1: lookup/delete an empty hash table, -ENOENT */
count = max_entries;
err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys,
values, &count, &opts);
CHECK((err && errno != ENOENT), "empty map",
"error: %s\n", strerror(errno));
/* populate elements to the map */
map_batch_update(map_fd, max_entries, keys, values, is_pcpu);
/* test 2: lookup/delete with count = 0, success */
count = 0;
err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys,
values, &count, &opts);
CHECK(err, "count = 0", "error: %s\n", strerror(errno));
/* test 3: lookup/delete with count = max_entries, success */
memset(keys, 0, max_entries * sizeof(*keys));
memset(values, 0, max_entries * value_size);
count = max_entries;
err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys,
values, &count, &opts);
CHECK((err && errno != ENOENT), "count = max_entries",
"error: %s\n", strerror(errno));
CHECK(count != max_entries, "count = max_entries",
"count = %u, max_entries = %u\n", count, max_entries);
map_batch_verify(visited, max_entries, keys, values, is_pcpu);
/* bpf_map_get_next_key() should return -ENOENT for an empty map. */
err = bpf_map_get_next_key(map_fd, NULL, &key);
CHECK(!err, "bpf_map_get_next_key()", "error: %s\n", strerror(errno));
/* test 4: lookup/delete in a loop with various steps. */
total_success = 0;
for (step = 1; step < max_entries; step++) {
map_batch_update(map_fd, max_entries, keys, values, is_pcpu);
memset(keys, 0, max_entries * sizeof(*keys));
memset(values, 0, max_entries * value_size);
total = 0;
/* iteratively lookup/delete elements with 'step'
* elements each
*/
count = step;
nospace_err = false;
while (true) {
err = bpf_map_lookup_batch(map_fd,
total ? &batch : NULL,
&batch, keys + total,
values +
total * value_size,
&count, &opts);
/* It is possible that we are failing due to buffer size
* not big enough. In such cases, let us just exit and
* go with large steps. Not that a buffer size with
* max_entries should always work.
*/
if (err && errno == ENOSPC) {
nospace_err = true;
break;
}
CHECK((err && errno != ENOENT), "lookup with steps",
"error: %s\n", strerror(errno));
total += count;
if (err)
break;
}
if (nospace_err == true)
continue;
CHECK(total != max_entries, "lookup with steps",
"total = %u, max_entries = %u\n", total, max_entries);
map_batch_verify(visited, max_entries, keys, values, is_pcpu);
total = 0;
count = step;
while (total < max_entries) {
if (max_entries - total < step)
count = max_entries - total;
err = bpf_map_delete_batch(map_fd,
keys + total,
&count, &opts);
CHECK((err && errno != ENOENT), "delete batch",
"error: %s\n", strerror(errno));
total += count;
if (err)
break;
}
CHECK(total != max_entries, "delete with steps",
"total = %u, max_entries = %u\n", total, max_entries);
/* check map is empty, errno == ENOENT */
err = bpf_map_get_next_key(map_fd, NULL, &key);
CHECK(!err || errno != ENOENT, "bpf_map_get_next_key()",
"error: %s\n", strerror(errno));
/* iteratively lookup/delete elements with 'step'
* elements each
*/
map_batch_update(map_fd, max_entries, keys, values, is_pcpu);
memset(keys, 0, max_entries * sizeof(*keys));
memset(values, 0, max_entries * value_size);
total = 0;
count = step;
nospace_err = false;
while (true) {
err = bpf_map_lookup_and_delete_batch(map_fd,
total ? &batch : NULL,
&batch, keys + total,
values +
total * value_size,
&count, &opts);
/* It is possible that we are failing due to buffer size
* not big enough. In such cases, let us just exit and
* go with large steps. Not that a buffer size with
* max_entries should always work.
*/
if (err && errno == ENOSPC) {
nospace_err = true;
break;
}
CHECK((err && errno != ENOENT), "lookup with steps",
"error: %s\n", strerror(errno));
total += count;
if (err)
break;
}
if (nospace_err == true)
continue;
CHECK(total != max_entries, "lookup/delete with steps",
"total = %u, max_entries = %u\n", total, max_entries);
map_batch_verify(visited, max_entries, keys, values, is_pcpu);
err = bpf_map_get_next_key(map_fd, NULL, &key);
CHECK(!err, "bpf_map_get_next_key()", "error: %s\n",
strerror(errno));
total_success++;
}
CHECK(total_success == 0, "check total_success",
"unexpected failure\n");
free(keys);
free(visited);
if (!is_pcpu)
free(values);
close(map_fd);
}
void htab_map_batch_ops(void)
{
__test_map_lookup_and_delete_batch(false);
printf("test_%s:PASS\n", __func__);
}
void htab_percpu_map_batch_ops(void)
{
__test_map_lookup_and_delete_batch(true);
printf("test_%s:PASS\n", __func__);
}
void test_htab_map_batch_ops(void)
{
htab_map_batch_ops();
htab_percpu_map_batch_ops();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-2.0
#include <arpa/inet.h>
#include <linux/bpf.h>
#include <netinet/in.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <test_maps.h>
struct test_lpm_key {
__u32 prefix;
struct in_addr ipv4;
};
static void map_batch_update(int map_fd, __u32 max_entries,
struct test_lpm_key *keys, int *values)
{
__u32 i;
int err;
char buff[16] = { 0 };
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
for (i = 0; i < max_entries; i++) {
keys[i].prefix = 32;
snprintf(buff, 16, "192.168.1.%d", i + 1);
inet_pton(AF_INET, buff, &keys[i].ipv4);
values[i] = i + 1;
}
err = bpf_map_update_batch(map_fd, keys, values, &max_entries, &opts);
CHECK(err, "bpf_map_update_batch()", "error:%s\n", strerror(errno));
}
static void map_batch_verify(int *visited, __u32 max_entries,
struct test_lpm_key *keys, int *values)
{
char buff[16] = { 0 };
int lower_byte = 0;
__u32 i;
memset(visited, 0, max_entries * sizeof(*visited));
for (i = 0; i < max_entries; i++) {
inet_ntop(AF_INET, &keys[i].ipv4, buff, 32);
CHECK(sscanf(buff, "192.168.1.%d", &lower_byte) == EOF,
"sscanf()", "error: i %d\n", i);
CHECK(lower_byte != values[i], "key/value checking",
"error: i %d key %s value %d\n", i, buff, values[i]);
visited[i] = 1;
}
for (i = 0; i < max_entries; i++) {
CHECK(visited[i] != 1, "visited checking",
"error: keys array at index %d missing\n", i);
}
}
void test_lpm_trie_map_batch_ops(void)
{
LIBBPF_OPTS(bpf_map_create_opts, create_opts, .map_flags = BPF_F_NO_PREALLOC);
struct test_lpm_key *keys, key;
int map_fd, *values, *visited;
__u32 step, count, total, total_success;
const __u32 max_entries = 10;
__u64 batch = 0;
int err;
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
map_fd = bpf_map_create(BPF_MAP_TYPE_LPM_TRIE, "lpm_trie_map",
sizeof(struct test_lpm_key), sizeof(int),
max_entries, &create_opts);
CHECK(map_fd == -1, "bpf_map_create()", "error:%s\n",
strerror(errno));
keys = malloc(max_entries * sizeof(struct test_lpm_key));
values = malloc(max_entries * sizeof(int));
visited = malloc(max_entries * sizeof(int));
CHECK(!keys || !values || !visited, "malloc()", "error:%s\n",
strerror(errno));
total_success = 0;
for (step = 1; step < max_entries; step++) {
map_batch_update(map_fd, max_entries, keys, values);
map_batch_verify(visited, max_entries, keys, values);
memset(keys, 0, max_entries * sizeof(*keys));
memset(values, 0, max_entries * sizeof(*values));
batch = 0;
total = 0;
/* iteratively lookup/delete elements with 'step'
* elements each.
*/
count = step;
while (true) {
err = bpf_map_lookup_batch(map_fd,
total ? &batch : NULL, &batch,
keys + total, values + total, &count, &opts);
CHECK((err && errno != ENOENT), "lookup with steps",
"error: %s\n", strerror(errno));
total += count;
if (err)
break;
}
CHECK(total != max_entries, "lookup with steps",
"total = %u, max_entries = %u\n", total, max_entries);
map_batch_verify(visited, max_entries, keys, values);
total = 0;
count = step;
while (total < max_entries) {
if (max_entries - total < step)
count = max_entries - total;
err = bpf_map_delete_batch(map_fd, keys + total, &count,
&opts);
CHECK((err && errno != ENOENT), "delete batch",
"error: %s\n", strerror(errno));
total += count;
if (err)
break;
}
CHECK(total != max_entries, "delete with steps",
"total = %u, max_entries = %u\n", total, max_entries);
/* check map is empty, errno == ENOENT */
err = bpf_map_get_next_key(map_fd, NULL, &key);
CHECK(!err || errno != ENOENT, "bpf_map_get_next_key()",
"error: %s\n", strerror(errno));
total_success++;
}
CHECK(total_success == 0, "check total_success",
"unexpected failure\n");
printf("%s:PASS\n", __func__);
free(keys);
free(values);
free(visited);
close(map_fd);
}
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <linux/bpf.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <test_maps.h>
struct test_lpm_key {
__u32 prefix;
__u32 data;
};
struct get_next_key_ctx {
struct test_lpm_key key;
bool start;
bool stop;
int map_fd;
int loop;
};
static void *get_next_key_fn(void *arg)
{
struct get_next_key_ctx *ctx = arg;
struct test_lpm_key next_key;
int i = 0;
while (!ctx->start)
usleep(1);
while (!ctx->stop && i++ < ctx->loop)
bpf_map_get_next_key(ctx->map_fd, &ctx->key, &next_key);
return NULL;
}
static void abort_get_next_key(struct get_next_key_ctx *ctx, pthread_t *tids,
unsigned int nr)
{
unsigned int i;
ctx->stop = true;
ctx->start = true;
for (i = 0; i < nr; i++)
pthread_join(tids[i], NULL);
}
/* This test aims to prevent regression of future. As long as the kernel does
* not panic, it is considered as success.
*/
void test_lpm_trie_map_get_next_key(void)
{
#define MAX_NR_THREADS 8
LIBBPF_OPTS(bpf_map_create_opts, create_opts,
.map_flags = BPF_F_NO_PREALLOC);
struct test_lpm_key key = {};
__u32 val = 0;
int map_fd;
const __u32 max_prefixlen = 8 * (sizeof(key) - sizeof(key.prefix));
const __u32 max_entries = max_prefixlen + 1;
unsigned int i, nr = MAX_NR_THREADS, loop = 65536;
pthread_t tids[MAX_NR_THREADS];
struct get_next_key_ctx ctx;
int err;
map_fd = bpf_map_create(BPF_MAP_TYPE_LPM_TRIE, "lpm_trie_map",
sizeof(struct test_lpm_key), sizeof(__u32),
max_entries, &create_opts);
CHECK(map_fd == -1, "bpf_map_create()", "error:%s\n",
strerror(errno));
for (i = 0; i <= max_prefixlen; i++) {
key.prefix = i;
err = bpf_map_update_elem(map_fd, &key, &val, BPF_ANY);
CHECK(err, "bpf_map_update_elem()", "error:%s\n",
strerror(errno));
}
ctx.start = false;
ctx.stop = false;
ctx.map_fd = map_fd;
ctx.loop = loop;
memcpy(&ctx.key, &key, sizeof(key));
for (i = 0; i < nr; i++) {
err = pthread_create(&tids[i], NULL, get_next_key_fn, &ctx);
if (err) {
abort_get_next_key(&ctx, tids, i);
CHECK(err, "pthread_create", "error %d\n", err);
}
}
ctx.start = true;
for (i = 0; i < nr; i++)
pthread_join(tids[i], NULL);
printf("%s:PASS\n", __func__);
close(map_fd);
}
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <test_maps.h>
#define OUTER_MAP_ENTRIES 10
static __u32 get_map_id_from_fd(int map_fd)
{
struct bpf_map_info map_info = {};
uint32_t info_len = sizeof(map_info);
int ret;
ret = bpf_map_get_info_by_fd(map_fd, &map_info, &info_len);
CHECK(ret < 0, "Finding map info failed", "error:%s\n",
strerror(errno));
return map_info.id;
}
/* This creates number of OUTER_MAP_ENTRIES maps that will be stored
* in outer map and return the created map_fds
*/
static void create_inner_maps(enum bpf_map_type map_type,
__u32 *inner_map_fds)
{
int map_fd, map_index, ret;
__u32 map_key = 0, map_id;
char map_name[16];
for (map_index = 0; map_index < OUTER_MAP_ENTRIES; map_index++) {
memset(map_name, 0, sizeof(map_name));
snprintf(map_name, sizeof(map_name), "inner_map_fd_%d", map_index);
map_fd = bpf_map_create(map_type, map_name, sizeof(__u32),
sizeof(__u32), 1, NULL);
CHECK(map_fd < 0,
"inner bpf_map_create() failed",
"map_type=(%d) map_name(%s), error:%s\n",
map_type, map_name, strerror(errno));
/* keep track of the inner map fd as it is required
* to add records in outer map
*/
inner_map_fds[map_index] = map_fd;
/* Add entry into this created map
* eg: map1 key = 0, value = map1's map id
* map2 key = 0, value = map2's map id
*/
map_id = get_map_id_from_fd(map_fd);
ret = bpf_map_update_elem(map_fd, &map_key, &map_id, 0);
CHECK(ret != 0,
"bpf_map_update_elem failed",
"map_type=(%d) map_name(%s), error:%s\n",
map_type, map_name, strerror(errno));
}
}
static int create_outer_map(enum bpf_map_type map_type, __u32 inner_map_fd)
{
int outer_map_fd;
LIBBPF_OPTS(bpf_map_create_opts, attr);
attr.inner_map_fd = inner_map_fd;
outer_map_fd = bpf_map_create(map_type, "outer_map", sizeof(__u32),
sizeof(__u32), OUTER_MAP_ENTRIES,
&attr);
CHECK(outer_map_fd < 0,
"outer bpf_map_create()",
"map_type=(%d), error:%s\n",
map_type, strerror(errno));
return outer_map_fd;
}
static void validate_fetch_results(int outer_map_fd,
__u32 *fetched_keys, __u32 *fetched_values,
__u32 max_entries_fetched)
{
__u32 inner_map_key, inner_map_value;
int inner_map_fd, entry, err;
__u32 outer_map_value;
for (entry = 0; entry < max_entries_fetched; ++entry) {
outer_map_value = fetched_values[entry];
inner_map_fd = bpf_map_get_fd_by_id(outer_map_value);
CHECK(inner_map_fd < 0,
"Failed to get inner map fd",
"from id(%d), error=%s\n",
outer_map_value, strerror(errno));
err = bpf_map_get_next_key(inner_map_fd, NULL, &inner_map_key);
CHECK(err != 0,
"Failed to get inner map key",
"error=%s\n", strerror(errno));
err = bpf_map_lookup_elem(inner_map_fd, &inner_map_key,
&inner_map_value);
close(inner_map_fd);
CHECK(err != 0,
"Failed to get inner map value",
"for key(%d), error=%s\n",
inner_map_key, strerror(errno));
/* Actual value validation */
CHECK(outer_map_value != inner_map_value,
"Failed to validate inner map value",
"fetched(%d) and lookedup(%d)!\n",
outer_map_value, inner_map_value);
}
}
static void fetch_and_validate(int outer_map_fd,
struct bpf_map_batch_opts *opts,
__u32 batch_size, bool delete_entries,
bool has_holes)
{
int err, max_entries = OUTER_MAP_ENTRIES - !!has_holes;
__u32 *fetched_keys, *fetched_values, total_fetched = 0, i;
__u32 batch_key = 0, fetch_count, step_size = batch_size;
__u32 value_size = sizeof(__u32);
/* Total entries needs to be fetched */
fetched_keys = calloc(max_entries, value_size);
fetched_values = calloc(max_entries, value_size);
CHECK((!fetched_keys || !fetched_values),
"Memory allocation failed for fetched_keys or fetched_values",
"error=%s\n", strerror(errno));
/* hash map may not always return full batch */
for (i = 0; i < OUTER_MAP_ENTRIES; i++) {
fetch_count = step_size;
err = delete_entries
? bpf_map_lookup_and_delete_batch(outer_map_fd,
total_fetched ? &batch_key : NULL,
&batch_key,
fetched_keys + total_fetched,
fetched_values + total_fetched,
&fetch_count, opts)
: bpf_map_lookup_batch(outer_map_fd,
total_fetched ? &batch_key : NULL,
&batch_key,
fetched_keys + total_fetched,
fetched_values + total_fetched,
&fetch_count, opts);
if (err && errno == ENOSPC) {
/* Fetch again with higher batch size */
total_fetched = 0;
step_size += batch_size;
continue;
}
CHECK((err < 0 && (errno != ENOENT)),
"lookup with steps failed",
"error: %s\n", strerror(errno));
/* Update the total fetched number */
total_fetched += fetch_count;
if (err)
break;
}
CHECK((total_fetched != max_entries),
"Unable to fetch expected entries !",
"total_fetched(%d) and max_entries(%d) error: (%d):%s\n",
total_fetched, max_entries, errno, strerror(errno));
/* validate the fetched entries */
validate_fetch_results(outer_map_fd, fetched_keys,
fetched_values, total_fetched);
printf("batch_op(%s) is successful with batch_size(%d)\n",
delete_entries ? "LOOKUP_AND_DELETE" : "LOOKUP", batch_size);
free(fetched_keys);
free(fetched_values);
}
static void _map_in_map_batch_ops(enum bpf_map_type outer_map_type,
enum bpf_map_type inner_map_type,
bool has_holes)
{
__u32 max_entries = OUTER_MAP_ENTRIES - !!has_holes;
__u32 *outer_map_keys, *inner_map_fds;
LIBBPF_OPTS(bpf_map_batch_opts, opts);
__u32 value_size = sizeof(__u32);
int batch_size[2] = {5, 10};
__u32 map_index, op_index;
int outer_map_fd, ret;
outer_map_keys = calloc(OUTER_MAP_ENTRIES, value_size);
inner_map_fds = calloc(OUTER_MAP_ENTRIES, value_size);
CHECK((!outer_map_keys || !inner_map_fds),
"Memory allocation failed for outer_map_keys or inner_map_fds",
"error=%s\n", strerror(errno));
create_inner_maps(inner_map_type, inner_map_fds);
outer_map_fd = create_outer_map(outer_map_type, *inner_map_fds);
/* create outer map keys */
for (map_index = 0; map_index < max_entries; map_index++)
outer_map_keys[map_index] =
((outer_map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
? 9 : 1000) - map_index;
/* This condition is only meaningful for array of maps.
*
* max_entries == OUTER_MAP_ENTRIES - 1 if it is true. Say
* max_entries is short for n, then outer_map_keys looks like:
*
* [n, n-1, ... 2, 1]
*
* We change it to
*
* [n, n-1, ... 2, 0]
*
* So it will leave key 1 as a hole. It will serve to test the
* correctness when batch on an array: a "non-exist" key might be
* actually allocated and returned from key iteration.
*/
if (has_holes)
outer_map_keys[max_entries - 1]--;
/* batch operation - map_update */
ret = bpf_map_update_batch(outer_map_fd, outer_map_keys,
inner_map_fds, &max_entries, &opts);
CHECK(ret != 0,
"Failed to update the outer map batch ops",
"error=%s\n", strerror(errno));
/* batch operation - map_lookup */
for (op_index = 0; op_index < 2; ++op_index)
fetch_and_validate(outer_map_fd, &opts,
batch_size[op_index], false,
has_holes);
/* batch operation - map_lookup_delete */
if (outer_map_type == BPF_MAP_TYPE_HASH_OF_MAPS)
fetch_and_validate(outer_map_fd, &opts,
max_entries, true /*delete*/,
has_holes);
/* close all map fds */
for (map_index = 0; map_index < OUTER_MAP_ENTRIES; map_index++)
close(inner_map_fds[map_index]);
close(outer_map_fd);
free(inner_map_fds);
free(outer_map_keys);
}
void test_map_in_map_batch_ops_array(void)
{
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_ARRAY, false);
printf("%s:PASS with inner ARRAY map\n", __func__);
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_HASH, false);
printf("%s:PASS with inner HASH map\n", __func__);
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_ARRAY, true);
printf("%s:PASS with inner ARRAY map with holes\n", __func__);
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_HASH, true);
printf("%s:PASS with inner HASH map with holes\n", __func__);
}
void test_map_in_map_batch_ops_hash(void)
{
_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_ARRAY, false);
printf("%s:PASS with inner ARRAY map\n", __func__);
_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_HASH, false);
printf("%s:PASS with inner HASH map\n", __func__);
}
@@ -0,0 +1,488 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2023 Isovalent */
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf_util.h>
#include <test_maps.h>
#include "map_percpu_stats.skel.h"
#define MAX_ENTRIES 16384
#define MAX_ENTRIES_HASH_OF_MAPS 64
#define N_THREADS 8
#define MAX_MAP_KEY_SIZE 4
#define PCPU_MIN_UNIT_SIZE 32768
static void map_info(int map_fd, struct bpf_map_info *info)
{
__u32 len = sizeof(*info);
int ret;
memset(info, 0, sizeof(*info));
ret = bpf_obj_get_info_by_fd(map_fd, info, &len);
CHECK(ret < 0, "bpf_obj_get_info_by_fd", "error: %s\n", strerror(errno));
}
static const char *map_type_to_s(__u32 type)
{
switch (type) {
case BPF_MAP_TYPE_HASH:
return "HASH";
case BPF_MAP_TYPE_PERCPU_HASH:
return "PERCPU_HASH";
case BPF_MAP_TYPE_LRU_HASH:
return "LRU_HASH";
case BPF_MAP_TYPE_LRU_PERCPU_HASH:
return "LRU_PERCPU_HASH";
case BPF_MAP_TYPE_HASH_OF_MAPS:
return "BPF_MAP_TYPE_HASH_OF_MAPS";
default:
return "<define-me>";
}
}
static __u32 map_count_elements(__u32 type, int map_fd)
{
__u32 key = -1;
int n = 0;
while (!bpf_map_get_next_key(map_fd, &key, &key))
n++;
return n;
}
#define BATCH true
static void delete_and_lookup_batch(int map_fd, void *keys, __u32 count)
{
static __u8 values[(8 << 10) * MAX_ENTRIES];
void *in_batch = NULL, *out_batch;
__u32 save_count = count;
int ret;
ret = bpf_map_lookup_and_delete_batch(map_fd,
&in_batch, &out_batch,
keys, values, &count,
NULL);
/*
* Despite what uapi header says, lookup_and_delete_batch will return
* -ENOENT in case we successfully have deleted all elements, so check
* this separately
*/
CHECK(ret < 0 && (errno != ENOENT || !count), "bpf_map_lookup_and_delete_batch",
"error: %s\n", strerror(errno));
CHECK(count != save_count,
"bpf_map_lookup_and_delete_batch",
"deleted not all elements: removed=%u expected=%u\n",
count, save_count);
}
static void delete_all_elements(__u32 type, int map_fd, bool batch)
{
static __u8 val[8 << 10]; /* enough for 1024 CPUs */
__u32 key = -1;
void *keys;
__u32 i, n;
int ret;
keys = calloc(MAX_MAP_KEY_SIZE, MAX_ENTRIES);
CHECK(!keys, "calloc", "error: %s\n", strerror(errno));
for (n = 0; !bpf_map_get_next_key(map_fd, &key, &key); n++)
memcpy(keys + n*MAX_MAP_KEY_SIZE, &key, MAX_MAP_KEY_SIZE);
if (batch) {
/* Can't mix delete_batch and delete_and_lookup_batch because
* they have different semantics in relation to the keys
* argument. However, delete_batch utilize map_delete_elem,
* so we actually test it in non-batch scenario */
delete_and_lookup_batch(map_fd, keys, n);
} else {
/* Intentionally mix delete and lookup_and_delete so we can test both */
for (i = 0; i < n; i++) {
void *keyp = keys + i*MAX_MAP_KEY_SIZE;
if (i % 2 || type == BPF_MAP_TYPE_HASH_OF_MAPS) {
ret = bpf_map_delete_elem(map_fd, keyp);
CHECK(ret < 0, "bpf_map_delete_elem",
"error: key %u: %s\n", i, strerror(errno));
} else {
ret = bpf_map_lookup_and_delete_elem(map_fd, keyp, val);
CHECK(ret < 0, "bpf_map_lookup_and_delete_elem",
"error: key %u: %s\n", i, strerror(errno));
}
}
}
free(keys);
}
static bool is_lru(__u32 map_type)
{
return map_type == BPF_MAP_TYPE_LRU_HASH ||
map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH;
}
static bool is_percpu(__u32 map_type)
{
return map_type == BPF_MAP_TYPE_PERCPU_HASH ||
map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH;
}
struct upsert_opts {
__u32 map_type;
int map_fd;
__u32 n;
bool retry_for_nomem;
};
static int create_small_hash(void)
{
int map_fd;
map_fd = bpf_map_create(BPF_MAP_TYPE_HASH, "small", 4, 4, 4, NULL);
CHECK(map_fd < 0, "bpf_map_create()", "error:%s (name=%s)\n",
strerror(errno), "small");
return map_fd;
}
static bool retry_for_nomem_fn(int err)
{
return err == ENOMEM;
}
static void *patch_map_thread(void *arg)
{
/* 8KB is enough for 1024 CPUs. And it is shared between N_THREADS. */
static __u8 blob[8 << 10];
struct upsert_opts *opts = arg;
void *val_ptr;
int val;
int ret;
int i;
for (i = 0; i < opts->n; i++) {
if (opts->map_type == BPF_MAP_TYPE_HASH_OF_MAPS) {
val = create_small_hash();
val_ptr = &val;
} else if (is_percpu(opts->map_type)) {
val_ptr = blob;
} else {
val = rand();
val_ptr = &val;
}
/* 2 seconds may be enough ? */
if (opts->retry_for_nomem)
ret = map_update_retriable(opts->map_fd, &i, val_ptr, 0,
40, retry_for_nomem_fn);
else
ret = bpf_map_update_elem(opts->map_fd, &i, val_ptr, 0);
CHECK(ret < 0, "bpf_map_update_elem", "key=%d error: %s\n", i, strerror(errno));
if (opts->map_type == BPF_MAP_TYPE_HASH_OF_MAPS)
close(val);
}
return NULL;
}
static void upsert_elements(struct upsert_opts *opts)
{
pthread_t threads[N_THREADS];
int ret;
int i;
for (i = 0; i < ARRAY_SIZE(threads); i++) {
ret = pthread_create(&i[threads], NULL, patch_map_thread, opts);
CHECK(ret != 0, "pthread_create", "error: %s\n", strerror(ret));
}
for (i = 0; i < ARRAY_SIZE(threads); i++) {
ret = pthread_join(i[threads], NULL);
CHECK(ret != 0, "pthread_join", "error: %s\n", strerror(ret));
}
}
static __u32 read_cur_elements(int iter_fd)
{
char buf[64];
ssize_t n;
__u32 ret;
n = read(iter_fd, buf, sizeof(buf)-1);
CHECK(n <= 0, "read", "error: %s\n", strerror(errno));
buf[n] = '\0';
errno = 0;
ret = (__u32)strtol(buf, NULL, 10);
CHECK(errno != 0, "strtol", "error: %s\n", strerror(errno));
return ret;
}
static __u32 get_cur_elements(int map_id)
{
struct map_percpu_stats *skel;
struct bpf_link *link;
__u32 n_elements;
int iter_fd;
int ret;
skel = map_percpu_stats__open();
CHECK(skel == NULL, "map_percpu_stats__open", "error: %s", strerror(errno));
skel->bss->target_id = map_id;
ret = map_percpu_stats__load(skel);
CHECK(ret != 0, "map_percpu_stats__load", "error: %s", strerror(errno));
link = bpf_program__attach_iter(skel->progs.dump_bpf_map, NULL);
CHECK(!link, "bpf_program__attach_iter", "error: %s\n", strerror(errno));
iter_fd = bpf_iter_create(bpf_link__fd(link));
CHECK(iter_fd < 0, "bpf_iter_create", "error: %s\n", strerror(errno));
n_elements = read_cur_elements(iter_fd);
close(iter_fd);
bpf_link__destroy(link);
map_percpu_stats__destroy(skel);
return n_elements;
}
static void check_expected_number_elements(__u32 n_inserted, int map_fd,
struct bpf_map_info *info)
{
__u32 n_real;
__u32 n_iter;
/* Count the current number of elements in the map by iterating through
* all the map keys via bpf_get_next_key
*/
n_real = map_count_elements(info->type, map_fd);
/* The "real" number of elements should be the same as the inserted
* number of elements in all cases except LRU maps, where some elements
* may have been evicted
*/
if (n_inserted == 0 || !is_lru(info->type))
CHECK(n_inserted != n_real, "map_count_elements",
"n_real(%u) != n_inserted(%u)\n", n_real, n_inserted);
/* Count the current number of elements in the map using an iterator */
n_iter = get_cur_elements(info->id);
/* Both counts should be the same, as all updates are over */
CHECK(n_iter != n_real, "get_cur_elements",
"n_iter=%u, expected %u (map_type=%s,map_flags=%08x)\n",
n_iter, n_real, map_type_to_s(info->type), info->map_flags);
}
static void __test(int map_fd)
{
struct upsert_opts opts = {
.map_fd = map_fd,
};
struct bpf_map_info info;
map_info(map_fd, &info);
opts.map_type = info.type;
opts.n = info.max_entries;
/* Reduce the number of elements we are updating such that we don't
* bump into -E2BIG from non-preallocated hash maps, but still will
* have some evictions for LRU maps */
if (opts.map_type != BPF_MAP_TYPE_HASH_OF_MAPS)
opts.n -= 512;
else
opts.n /= 2;
/* per-cpu bpf memory allocator may not be able to allocate per-cpu
* pointer successfully and it can not refill free llist timely, and
* bpf_map_update_elem() will return -ENOMEM. so just retry to mitigate
* the problem temporarily.
*/
opts.retry_for_nomem = is_percpu(opts.map_type) && (info.map_flags & BPF_F_NO_PREALLOC);
/*
* Upsert keys [0, n) under some competition: with random values from
* N_THREADS threads. Check values, then delete all elements and check
* values again.
*/
upsert_elements(&opts);
check_expected_number_elements(opts.n, map_fd, &info);
delete_all_elements(info.type, map_fd, !BATCH);
check_expected_number_elements(0, map_fd, &info);
/* Now do the same, but using batch delete operations */
upsert_elements(&opts);
check_expected_number_elements(opts.n, map_fd, &info);
delete_all_elements(info.type, map_fd, BATCH);
check_expected_number_elements(0, map_fd, &info);
close(map_fd);
}
static int map_create_opts(__u32 type, const char *name,
struct bpf_map_create_opts *map_opts,
__u32 key_size, __u32 val_size)
{
int max_entries;
int map_fd;
if (type == BPF_MAP_TYPE_HASH_OF_MAPS)
max_entries = MAX_ENTRIES_HASH_OF_MAPS;
else
max_entries = MAX_ENTRIES;
map_fd = bpf_map_create(type, name, key_size, val_size, max_entries, map_opts);
CHECK(map_fd < 0, "bpf_map_create()", "error:%s (name=%s)\n",
strerror(errno), name);
return map_fd;
}
static int map_create(__u32 type, const char *name, struct bpf_map_create_opts *map_opts)
{
return map_create_opts(type, name, map_opts, sizeof(int), sizeof(int));
}
static int create_hash(void)
{
LIBBPF_OPTS(bpf_map_create_opts, map_opts, .map_flags = BPF_F_NO_PREALLOC);
return map_create(BPF_MAP_TYPE_HASH, "hash", &map_opts);
}
static int create_percpu_hash(void)
{
LIBBPF_OPTS(bpf_map_create_opts, map_opts, .map_flags = BPF_F_NO_PREALLOC);
return map_create(BPF_MAP_TYPE_PERCPU_HASH, "percpu_hash", &map_opts);
}
static int create_hash_prealloc(void)
{
return map_create(BPF_MAP_TYPE_HASH, "hash", NULL);
}
static int create_percpu_hash_prealloc(void)
{
return map_create(BPF_MAP_TYPE_PERCPU_HASH, "percpu_hash_prealloc", NULL);
}
static int create_lru_hash(__u32 type, __u32 map_flags)
{
LIBBPF_OPTS(bpf_map_create_opts, map_opts, .map_flags = map_flags);
return map_create(type, "lru_hash", &map_opts);
}
static int create_hash_of_maps(void)
{
LIBBPF_OPTS(bpf_map_create_opts, map_opts,
.map_flags = BPF_F_NO_PREALLOC,
.inner_map_fd = create_small_hash(),
);
int ret;
ret = map_create_opts(BPF_MAP_TYPE_HASH_OF_MAPS, "hash_of_maps",
&map_opts, sizeof(int), sizeof(int));
close(map_opts.inner_map_fd);
return ret;
}
static void map_percpu_stats_hash(void)
{
__test(create_hash());
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_percpu_hash(void)
{
__test(create_percpu_hash());
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_hash_prealloc(void)
{
__test(create_hash_prealloc());
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_percpu_hash_prealloc(void)
{
__test(create_percpu_hash_prealloc());
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_lru_hash(void)
{
__test(create_lru_hash(BPF_MAP_TYPE_LRU_HASH, 0));
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_lru_hash_no_common(void)
{
__test(create_lru_hash(BPF_MAP_TYPE_LRU_HASH, BPF_F_NO_COMMON_LRU));
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_percpu_lru_hash(void)
{
__test(create_lru_hash(BPF_MAP_TYPE_LRU_PERCPU_HASH, 0));
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_percpu_lru_hash_no_common(void)
{
__test(create_lru_hash(BPF_MAP_TYPE_LRU_PERCPU_HASH, BPF_F_NO_COMMON_LRU));
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_hash_of_maps(void)
{
__test(create_hash_of_maps());
printf("test_%s:PASS\n", __func__);
}
static void map_percpu_stats_map_value_size(void)
{
int fd;
int value_sz = PCPU_MIN_UNIT_SIZE + 1;
struct bpf_map_create_opts opts = { .sz = sizeof(opts) };
enum bpf_map_type map_types[] = { BPF_MAP_TYPE_PERCPU_ARRAY,
BPF_MAP_TYPE_PERCPU_HASH,
BPF_MAP_TYPE_LRU_PERCPU_HASH };
for (int i = 0; i < ARRAY_SIZE(map_types); i++) {
fd = bpf_map_create(map_types[i], NULL, sizeof(__u32), value_sz, 1, &opts);
CHECK(fd < 0 && errno != E2BIG, "percpu map value size",
"error: %s\n", strerror(errno));
}
printf("test_%s:PASS\n", __func__);
}
void test_map_percpu_stats(void)
{
map_percpu_stats_hash();
map_percpu_stats_percpu_hash();
map_percpu_stats_hash_prealloc();
map_percpu_stats_percpu_hash_prealloc();
map_percpu_stats_lru_hash();
map_percpu_stats_lru_hash_no_common();
map_percpu_stats_percpu_lru_hash();
map_percpu_stats_percpu_lru_hash_no_common();
map_percpu_stats_hash_of_maps();
map_percpu_stats_map_value_size();
}
@@ -0,0 +1,627 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019 Facebook */
#include <linux/compiler.h>
#include <linux/err.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/btf.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <test_btf.h>
#include <test_maps.h>
static struct bpf_map_create_opts map_opts = {
.sz = sizeof(map_opts),
.btf_key_type_id = 1,
.btf_value_type_id = 3,
.btf_fd = -1,
.map_flags = BPF_F_NO_PREALLOC,
};
static unsigned int nr_sk_threads_done;
static unsigned int nr_sk_threads_err;
static unsigned int nr_sk_per_thread = 4096;
static unsigned int nr_sk_threads = 4;
static int sk_storage_map = -1;
static unsigned int stop;
static int runtime_s = 5;
static bool is_stopped(void)
{
return READ_ONCE(stop);
}
static unsigned int threads_err(void)
{
return READ_ONCE(nr_sk_threads_err);
}
static void notify_thread_err(void)
{
__sync_add_and_fetch(&nr_sk_threads_err, 1);
}
static bool wait_for_threads_err(void)
{
while (!is_stopped() && !threads_err())
usleep(500);
return !is_stopped();
}
static unsigned int threads_done(void)
{
return READ_ONCE(nr_sk_threads_done);
}
static void notify_thread_done(void)
{
__sync_add_and_fetch(&nr_sk_threads_done, 1);
}
static void notify_thread_redo(void)
{
__sync_sub_and_fetch(&nr_sk_threads_done, 1);
}
static bool wait_for_threads_done(void)
{
while (threads_done() != nr_sk_threads && !is_stopped() &&
!threads_err())
usleep(50);
return !is_stopped() && !threads_err();
}
static bool wait_for_threads_redo(void)
{
while (threads_done() && !is_stopped() && !threads_err())
usleep(50);
return !is_stopped() && !threads_err();
}
static bool wait_for_map(void)
{
while (READ_ONCE(sk_storage_map) == -1 && !is_stopped())
usleep(50);
return !is_stopped();
}
static bool wait_for_map_close(void)
{
while (READ_ONCE(sk_storage_map) != -1 && !is_stopped())
;
return !is_stopped();
}
static int load_btf(void)
{
const char btf_str_sec[] = "\0bpf_spin_lock\0val\0cnt\0l";
__u32 btf_raw_types[] = {
/* int */
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
/* struct bpf_spin_lock */ /* [2] */
BTF_TYPE_ENC(1, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 4),
BTF_MEMBER_ENC(15, 1, 0), /* int val; */
/* struct val */ /* [3] */
BTF_TYPE_ENC(15, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 8),
BTF_MEMBER_ENC(19, 1, 0), /* int cnt; */
BTF_MEMBER_ENC(23, 2, 32),/* struct bpf_spin_lock l; */
};
struct btf_header btf_hdr = {
.magic = BTF_MAGIC,
.version = BTF_VERSION,
.hdr_len = sizeof(struct btf_header),
.type_len = sizeof(btf_raw_types),
.str_off = sizeof(btf_raw_types),
.str_len = sizeof(btf_str_sec),
};
__u8 raw_btf[sizeof(struct btf_header) + sizeof(btf_raw_types) +
sizeof(btf_str_sec)];
memcpy(raw_btf, &btf_hdr, sizeof(btf_hdr));
memcpy(raw_btf + sizeof(btf_hdr), btf_raw_types, sizeof(btf_raw_types));
memcpy(raw_btf + sizeof(btf_hdr) + sizeof(btf_raw_types),
btf_str_sec, sizeof(btf_str_sec));
return bpf_btf_load(raw_btf, sizeof(raw_btf), NULL);
}
static int create_sk_storage_map(void)
{
int btf_fd, map_fd;
btf_fd = load_btf();
CHECK(btf_fd == -1, "bpf_load_btf", "btf_fd:%d errno:%d\n",
btf_fd, errno);
map_opts.btf_fd = btf_fd;
map_fd = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &map_opts);
map_opts.btf_fd = -1;
close(btf_fd);
CHECK(map_fd == -1,
"bpf_map_create()", "errno:%d\n", errno);
return map_fd;
}
static void *insert_close_thread(void *arg)
{
struct {
int cnt;
int lock;
} value = { .cnt = 0xeB9F, .lock = 0, };
int i, map_fd, err, *sk_fds;
sk_fds = malloc(sizeof(*sk_fds) * nr_sk_per_thread);
if (!sk_fds) {
notify_thread_err();
return ERR_PTR(-ENOMEM);
}
for (i = 0; i < nr_sk_per_thread; i++)
sk_fds[i] = -1;
while (!is_stopped()) {
if (!wait_for_map())
goto close_all;
map_fd = READ_ONCE(sk_storage_map);
for (i = 0; i < nr_sk_per_thread && !is_stopped(); i++) {
sk_fds[i] = socket(AF_INET6, SOCK_STREAM, 0);
if (sk_fds[i] == -1) {
err = -errno;
fprintf(stderr, "socket(): errno:%d\n", errno);
goto errout;
}
err = bpf_map_update_elem(map_fd, &sk_fds[i], &value,
BPF_NOEXIST);
if (err) {
err = -errno;
fprintf(stderr,
"bpf_map_update_elem(): errno:%d\n",
errno);
goto errout;
}
}
notify_thread_done();
wait_for_map_close();
close_all:
for (i = 0; i < nr_sk_per_thread; i++) {
close(sk_fds[i]);
sk_fds[i] = -1;
}
notify_thread_redo();
}
free(sk_fds);
return NULL;
errout:
for (i = 0; i < nr_sk_per_thread && sk_fds[i] != -1; i++)
close(sk_fds[i]);
free(sk_fds);
notify_thread_err();
return ERR_PTR(err);
}
static int do_sk_storage_map_stress_free(void)
{
int i, map_fd = -1, err = 0, nr_threads_created = 0;
pthread_t *sk_thread_ids;
void *thread_ret;
sk_thread_ids = malloc(sizeof(pthread_t) * nr_sk_threads);
if (!sk_thread_ids) {
fprintf(stderr, "malloc(sk_threads): NULL\n");
return -ENOMEM;
}
for (i = 0; i < nr_sk_threads; i++) {
err = pthread_create(&sk_thread_ids[i], NULL,
insert_close_thread, NULL);
if (err) {
err = -errno;
goto done;
}
nr_threads_created++;
}
while (!is_stopped()) {
map_fd = create_sk_storage_map();
WRITE_ONCE(sk_storage_map, map_fd);
if (!wait_for_threads_done())
break;
WRITE_ONCE(sk_storage_map, -1);
close(map_fd);
map_fd = -1;
if (!wait_for_threads_redo())
break;
}
done:
WRITE_ONCE(stop, 1);
for (i = 0; i < nr_threads_created; i++) {
pthread_join(sk_thread_ids[i], &thread_ret);
if (IS_ERR(thread_ret) && !err) {
err = PTR_ERR(thread_ret);
fprintf(stderr, "threads#%u: err:%d\n", i, err);
}
}
free(sk_thread_ids);
if (map_fd != -1)
close(map_fd);
return err;
}
static void *update_thread(void *arg)
{
struct {
int cnt;
int lock;
} value = { .cnt = 0xeB9F, .lock = 0, };
int map_fd = READ_ONCE(sk_storage_map);
int sk_fd = *(int *)arg;
int err = 0; /* Suppress compiler false alarm */
while (!is_stopped()) {
err = bpf_map_update_elem(map_fd, &sk_fd, &value, 0);
if (err && errno != EAGAIN) {
err = -errno;
fprintf(stderr, "bpf_map_update_elem: %d %d\n",
err, errno);
break;
}
}
if (!is_stopped()) {
notify_thread_err();
return ERR_PTR(err);
}
return NULL;
}
static void *delete_thread(void *arg)
{
int map_fd = READ_ONCE(sk_storage_map);
int sk_fd = *(int *)arg;
int err = 0; /* Suppress compiler false alarm */
while (!is_stopped()) {
err = bpf_map_delete_elem(map_fd, &sk_fd);
if (err && errno != ENOENT) {
err = -errno;
fprintf(stderr, "bpf_map_delete_elem: %d %d\n",
err, errno);
break;
}
}
if (!is_stopped()) {
notify_thread_err();
return ERR_PTR(err);
}
return NULL;
}
static int do_sk_storage_map_stress_change(void)
{
int i, sk_fd, map_fd = -1, err = 0, nr_threads_created = 0;
pthread_t *sk_thread_ids;
void *thread_ret;
sk_thread_ids = malloc(sizeof(pthread_t) * nr_sk_threads);
if (!sk_thread_ids) {
fprintf(stderr, "malloc(sk_threads): NULL\n");
return -ENOMEM;
}
sk_fd = socket(AF_INET6, SOCK_STREAM, 0);
if (sk_fd == -1) {
err = -errno;
goto done;
}
map_fd = create_sk_storage_map();
WRITE_ONCE(sk_storage_map, map_fd);
for (i = 0; i < nr_sk_threads; i++) {
if (i & 0x1)
err = pthread_create(&sk_thread_ids[i], NULL,
update_thread, &sk_fd);
else
err = pthread_create(&sk_thread_ids[i], NULL,
delete_thread, &sk_fd);
if (err) {
err = -errno;
goto done;
}
nr_threads_created++;
}
wait_for_threads_err();
done:
WRITE_ONCE(stop, 1);
for (i = 0; i < nr_threads_created; i++) {
pthread_join(sk_thread_ids[i], &thread_ret);
if (IS_ERR(thread_ret) && !err) {
err = PTR_ERR(thread_ret);
fprintf(stderr, "threads#%u: err:%d\n", i, err);
}
}
free(sk_thread_ids);
if (sk_fd != -1)
close(sk_fd);
close(map_fd);
return err;
}
static void stop_handler(int signum)
{
if (signum != SIGALRM)
printf("stopping...\n");
WRITE_ONCE(stop, 1);
}
#define BPF_SK_STORAGE_MAP_TEST_NR_THREADS "BPF_SK_STORAGE_MAP_TEST_NR_THREADS"
#define BPF_SK_STORAGE_MAP_TEST_SK_PER_THREAD "BPF_SK_STORAGE_MAP_TEST_SK_PER_THREAD"
#define BPF_SK_STORAGE_MAP_TEST_RUNTIME_S "BPF_SK_STORAGE_MAP_TEST_RUNTIME_S"
#define BPF_SK_STORAGE_MAP_TEST_NAME "BPF_SK_STORAGE_MAP_TEST_NAME"
static void test_sk_storage_map_stress_free(void)
{
struct rlimit rlim_old, rlim_new = {};
int err;
getrlimit(RLIMIT_NOFILE, &rlim_old);
signal(SIGTERM, stop_handler);
signal(SIGINT, stop_handler);
if (runtime_s > 0) {
signal(SIGALRM, stop_handler);
alarm(runtime_s);
}
if (rlim_old.rlim_cur < nr_sk_threads * nr_sk_per_thread) {
rlim_new.rlim_cur = nr_sk_threads * nr_sk_per_thread + 128;
rlim_new.rlim_max = rlim_new.rlim_cur + 128;
err = setrlimit(RLIMIT_NOFILE, &rlim_new);
CHECK(err, "setrlimit(RLIMIT_NOFILE)", "rlim_new:%lu errno:%d",
(unsigned long) rlim_new.rlim_cur, errno);
}
err = do_sk_storage_map_stress_free();
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
if (runtime_s > 0) {
signal(SIGALRM, SIG_DFL);
alarm(0);
}
if (rlim_new.rlim_cur)
setrlimit(RLIMIT_NOFILE, &rlim_old);
CHECK(err, "test_sk_storage_map_stress_free", "err:%d\n", err);
}
static void test_sk_storage_map_stress_change(void)
{
int err;
signal(SIGTERM, stop_handler);
signal(SIGINT, stop_handler);
if (runtime_s > 0) {
signal(SIGALRM, stop_handler);
alarm(runtime_s);
}
err = do_sk_storage_map_stress_change();
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
if (runtime_s > 0) {
signal(SIGALRM, SIG_DFL);
alarm(0);
}
CHECK(err, "test_sk_storage_map_stress_change", "err:%d\n", err);
}
static void test_sk_storage_map_basic(void)
{
struct {
int cnt;
int lock;
} value = { .cnt = 0xeB9f, .lock = 1, }, lookup_value;
struct bpf_map_create_opts bad_xattr;
int btf_fd, map_fd, sk_fd, err;
btf_fd = load_btf();
CHECK(btf_fd == -1, "bpf_load_btf", "btf_fd:%d errno:%d\n",
btf_fd, errno);
map_opts.btf_fd = btf_fd;
sk_fd = socket(AF_INET6, SOCK_STREAM, 0);
CHECK(sk_fd == -1, "socket()", "sk_fd:%d errno:%d\n",
sk_fd, errno);
map_fd = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &map_opts);
CHECK(map_fd == -1, "bpf_map_create(good_xattr)",
"map_fd:%d errno:%d\n", map_fd, errno);
/* Add new elem */
memcpy(&lookup_value, &value, sizeof(value));
err = bpf_map_update_elem(map_fd, &sk_fd, &value,
BPF_NOEXIST | BPF_F_LOCK);
CHECK(err, "bpf_map_update_elem(BPF_NOEXIST|BPF_F_LOCK)",
"err:%d errno:%d\n", err, errno);
err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value,
BPF_F_LOCK);
CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt,
"bpf_map_lookup_elem_flags(BPF_F_LOCK)",
"err:%d errno:%d lock:%x cnt:%x(%x)\n",
err, errno, lookup_value.lock, lookup_value.cnt, value.cnt);
/* Bump the cnt and update with BPF_EXIST | BPF_F_LOCK */
value.cnt += 1;
value.lock = 2;
err = bpf_map_update_elem(map_fd, &sk_fd, &value,
BPF_EXIST | BPF_F_LOCK);
CHECK(err, "bpf_map_update_elem(BPF_EXIST|BPF_F_LOCK)",
"err:%d errno:%d\n", err, errno);
err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value,
BPF_F_LOCK);
CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt,
"bpf_map_lookup_elem_flags(BPF_F_LOCK)",
"err:%d errno:%d lock:%x cnt:%x(%x)\n",
err, errno, lookup_value.lock, lookup_value.cnt, value.cnt);
/* Bump the cnt and update with BPF_EXIST */
value.cnt += 1;
value.lock = 2;
err = bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_EXIST);
CHECK(err, "bpf_map_update_elem(BPF_EXIST)",
"err:%d errno:%d\n", err, errno);
err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value,
BPF_F_LOCK);
CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt,
"bpf_map_lookup_elem_flags(BPF_F_LOCK)",
"err:%d errno:%d lock:%x cnt:%x(%x)\n",
err, errno, lookup_value.lock, lookup_value.cnt, value.cnt);
/* Update with BPF_NOEXIST */
value.cnt += 1;
value.lock = 2;
err = bpf_map_update_elem(map_fd, &sk_fd, &value,
BPF_NOEXIST | BPF_F_LOCK);
CHECK(!err || errno != EEXIST,
"bpf_map_update_elem(BPF_NOEXIST|BPF_F_LOCK)",
"err:%d errno:%d\n", err, errno);
err = bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_NOEXIST);
CHECK(!err || errno != EEXIST, "bpf_map_update_elem(BPF_NOEXIST)",
"err:%d errno:%d\n", err, errno);
value.cnt -= 1;
err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value,
BPF_F_LOCK);
CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt,
"bpf_map_lookup_elem_flags(BPF_F_LOCK)",
"err:%d errno:%d lock:%x cnt:%x(%x)\n",
err, errno, lookup_value.lock, lookup_value.cnt, value.cnt);
/* Bump the cnt again and update with map_flags == 0 */
value.cnt += 1;
value.lock = 2;
err = bpf_map_update_elem(map_fd, &sk_fd, &value, 0);
CHECK(err, "bpf_map_update_elem()", "err:%d errno:%d\n",
err, errno);
err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value,
BPF_F_LOCK);
CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt,
"bpf_map_lookup_elem_flags(BPF_F_LOCK)",
"err:%d errno:%d lock:%x cnt:%x(%x)\n",
err, errno, lookup_value.lock, lookup_value.cnt, value.cnt);
/* Test delete elem */
err = bpf_map_delete_elem(map_fd, &sk_fd);
CHECK(err, "bpf_map_delete_elem()", "err:%d errno:%d\n",
err, errno);
err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value,
BPF_F_LOCK);
CHECK(!err || errno != ENOENT,
"bpf_map_lookup_elem_flags(BPF_F_LOCK)",
"err:%d errno:%d\n", err, errno);
err = bpf_map_delete_elem(map_fd, &sk_fd);
CHECK(!err || errno != ENOENT, "bpf_map_delete_elem()",
"err:%d errno:%d\n", err, errno);
memcpy(&bad_xattr, &map_opts, sizeof(map_opts));
bad_xattr.btf_key_type_id = 0;
err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &bad_xattr);
CHECK(!err || errno != EINVAL, "bpf_map_create(bad_xattr)",
"err:%d errno:%d\n", err, errno);
memcpy(&bad_xattr, &map_opts, sizeof(map_opts));
bad_xattr.btf_key_type_id = 3;
err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &bad_xattr);
CHECK(!err || errno != EINVAL, "bpf_map_create(bad_xattr)",
"err:%d errno:%d\n", err, errno);
err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 1, &map_opts);
CHECK(!err || errno != EINVAL, "bpf_map_create(bad_xattr)",
"err:%d errno:%d\n", err, errno);
memcpy(&bad_xattr, &map_opts, sizeof(map_opts));
bad_xattr.map_flags = 0;
err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &bad_xattr);
CHECK(!err || errno != EINVAL, "bap_create_map_xattr(bad_xattr)",
"err:%d errno:%d\n", err, errno);
map_opts.btf_fd = -1;
close(btf_fd);
close(map_fd);
close(sk_fd);
}
void test_sk_storage_map(void)
{
const char *test_name, *env_opt;
bool test_ran = false;
test_name = getenv(BPF_SK_STORAGE_MAP_TEST_NAME);
env_opt = getenv(BPF_SK_STORAGE_MAP_TEST_NR_THREADS);
if (env_opt)
nr_sk_threads = atoi(env_opt);
env_opt = getenv(BPF_SK_STORAGE_MAP_TEST_SK_PER_THREAD);
if (env_opt)
nr_sk_per_thread = atoi(env_opt);
env_opt = getenv(BPF_SK_STORAGE_MAP_TEST_RUNTIME_S);
if (env_opt)
runtime_s = atoi(env_opt);
if (!test_name || !strcmp(test_name, "basic")) {
test_sk_storage_map_basic();
test_ran = true;
}
if (!test_name || !strcmp(test_name, "stress_free")) {
test_sk_storage_map_stress_free();
test_ran = true;
}
if (!test_name || !strcmp(test_name, "stress_change")) {
test_sk_storage_map_stress_change();
test_ran = true;
}
if (test_ran)
printf("%s:PASS\n", __func__);
else
CHECK(1, "Invalid test_name", "%s\n", test_name);
}