Skip to content

Commit dbf7cc5

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-skip-non-exist-keys-in-generic_map_lookup_batch'
Yan Zhai says: ==================== bpf: skip non exist keys in generic_map_lookup_batch The generic_map_lookup_batch currently returns EINTR if it fails with ENOENT and retries several times on bpf_map_copy_value. The next batch would start from the same location, presuming it's a transient issue. This is incorrect if a map can actually have "holes", i.e. "get_next_key" can return a key that does not point to a valid value. At least the array of maps type may contain such holes legitly. Right now these holes show up, generic batch lookup cannot proceed any more. It will always fail with EINTR errors. This patch fixes this behavior by skipping the non-existing key, and does not return EINTR any more. V2->V3: deleted a unused macro V1->V2: split the fix and selftests; fixed a few selftests issues. V2: https://lore.kernel.org/bpf/cover.1738905497.git.yan@cloudflare.com/ V1: https://lore.kernel.org/bpf/Z6OYbS4WqQnmzi2z@debian.debian/ ==================== Link: https://patch.msgid.link/cover.1739171594.git.yan@cloudflare.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2 parents 8784714 + d66b773 commit dbf7cc5

File tree

2 files changed

+49
-31
lines changed

2 files changed

+49
-31
lines changed

kernel/bpf/syscall.c

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,8 +1977,6 @@ int generic_map_update_batch(struct bpf_map *map, struct file *map_file,
19771977
return err;
19781978
}
19791979

1980-
#define MAP_LOOKUP_RETRIES 3
1981-
19821980
int generic_map_lookup_batch(struct bpf_map *map,
19831981
const union bpf_attr *attr,
19841982
union bpf_attr __user *uattr)
@@ -1988,8 +1986,8 @@ int generic_map_lookup_batch(struct bpf_map *map,
19881986
void __user *values = u64_to_user_ptr(attr->batch.values);
19891987
void __user *keys = u64_to_user_ptr(attr->batch.keys);
19901988
void *buf, *buf_prevkey, *prev_key, *key, *value;
1991-
int err, retry = MAP_LOOKUP_RETRIES;
19921989
u32 value_size, cp, max_count;
1990+
int err;
19931991

19941992
if (attr->batch.elem_flags & ~BPF_F_LOCK)
19951993
return -EINVAL;
@@ -2035,14 +2033,8 @@ int generic_map_lookup_batch(struct bpf_map *map,
20352033
err = bpf_map_copy_value(map, key, value,
20362034
attr->batch.elem_flags);
20372035

2038-
if (err == -ENOENT) {
2039-
if (retry) {
2040-
retry--;
2041-
continue;
2042-
}
2043-
err = -EINTR;
2044-
break;
2045-
}
2036+
if (err == -ENOENT)
2037+
goto next_key;
20462038

20472039
if (err)
20482040
goto free_buf;
@@ -2057,12 +2049,12 @@ int generic_map_lookup_batch(struct bpf_map *map,
20572049
goto free_buf;
20582050
}
20592051

2052+
cp++;
2053+
next_key:
20602054
if (!prev_key)
20612055
prev_key = buf_prevkey;
20622056

20632057
swap(prev_key, key);
2064-
retry = MAP_LOOKUP_RETRIES;
2065-
cp++;
20662058
cond_resched();
20672059
}
20682060

tools/testing/selftests/bpf/map_tests/map_in_map_batch_ops.c

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,12 @@ static void validate_fetch_results(int outer_map_fd,
120120

121121
static void fetch_and_validate(int outer_map_fd,
122122
struct bpf_map_batch_opts *opts,
123-
__u32 batch_size, bool delete_entries)
123+
__u32 batch_size, bool delete_entries,
124+
bool has_holes)
124125
{
125-
__u32 *fetched_keys, *fetched_values, total_fetched = 0;
126-
__u32 batch_key = 0, fetch_count, step_size;
127-
int err, max_entries = OUTER_MAP_ENTRIES;
126+
int err, max_entries = OUTER_MAP_ENTRIES - !!has_holes;
127+
__u32 *fetched_keys, *fetched_values, total_fetched = 0, i;
128+
__u32 batch_key = 0, fetch_count, step_size = batch_size;
128129
__u32 value_size = sizeof(__u32);
129130

130131
/* Total entries needs to be fetched */
@@ -134,9 +135,8 @@ static void fetch_and_validate(int outer_map_fd,
134135
"Memory allocation failed for fetched_keys or fetched_values",
135136
"error=%s\n", strerror(errno));
136137

137-
for (step_size = batch_size;
138-
step_size <= max_entries;
139-
step_size += batch_size) {
138+
/* hash map may not always return full batch */
139+
for (i = 0; i < OUTER_MAP_ENTRIES; i++) {
140140
fetch_count = step_size;
141141
err = delete_entries
142142
? bpf_map_lookup_and_delete_batch(outer_map_fd,
@@ -155,6 +155,7 @@ static void fetch_and_validate(int outer_map_fd,
155155
if (err && errno == ENOSPC) {
156156
/* Fetch again with higher batch size */
157157
total_fetched = 0;
158+
step_size += batch_size;
158159
continue;
159160
}
160161

@@ -184,18 +185,19 @@ static void fetch_and_validate(int outer_map_fd,
184185
}
185186

186187
static void _map_in_map_batch_ops(enum bpf_map_type outer_map_type,
187-
enum bpf_map_type inner_map_type)
188+
enum bpf_map_type inner_map_type,
189+
bool has_holes)
188190
{
191+
__u32 max_entries = OUTER_MAP_ENTRIES - !!has_holes;
189192
__u32 *outer_map_keys, *inner_map_fds;
190-
__u32 max_entries = OUTER_MAP_ENTRIES;
191193
LIBBPF_OPTS(bpf_map_batch_opts, opts);
192194
__u32 value_size = sizeof(__u32);
193195
int batch_size[2] = {5, 10};
194196
__u32 map_index, op_index;
195197
int outer_map_fd, ret;
196198

197-
outer_map_keys = calloc(max_entries, value_size);
198-
inner_map_fds = calloc(max_entries, value_size);
199+
outer_map_keys = calloc(OUTER_MAP_ENTRIES, value_size);
200+
inner_map_fds = calloc(OUTER_MAP_ENTRIES, value_size);
199201
CHECK((!outer_map_keys || !inner_map_fds),
200202
"Memory allocation failed for outer_map_keys or inner_map_fds",
201203
"error=%s\n", strerror(errno));
@@ -209,6 +211,24 @@ static void _map_in_map_batch_ops(enum bpf_map_type outer_map_type,
209211
((outer_map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
210212
? 9 : 1000) - map_index;
211213

214+
/* This condition is only meaningful for array of maps.
215+
*
216+
* max_entries == OUTER_MAP_ENTRIES - 1 if it is true. Say
217+
* max_entries is short for n, then outer_map_keys looks like:
218+
*
219+
* [n, n-1, ... 2, 1]
220+
*
221+
* We change it to
222+
*
223+
* [n, n-1, ... 2, 0]
224+
*
225+
* So it will leave key 1 as a hole. It will serve to test the
226+
* correctness when batch on an array: a "non-exist" key might be
227+
* actually allocated and returned from key iteration.
228+
*/
229+
if (has_holes)
230+
outer_map_keys[max_entries - 1]--;
231+
212232
/* batch operation - map_update */
213233
ret = bpf_map_update_batch(outer_map_fd, outer_map_keys,
214234
inner_map_fds, &max_entries, &opts);
@@ -219,15 +239,17 @@ static void _map_in_map_batch_ops(enum bpf_map_type outer_map_type,
219239
/* batch operation - map_lookup */
220240
for (op_index = 0; op_index < 2; ++op_index)
221241
fetch_and_validate(outer_map_fd, &opts,
222-
batch_size[op_index], false);
242+
batch_size[op_index], false,
243+
has_holes);
223244

224245
/* batch operation - map_lookup_delete */
225246
if (outer_map_type == BPF_MAP_TYPE_HASH_OF_MAPS)
226247
fetch_and_validate(outer_map_fd, &opts,
227-
max_entries, true /*delete*/);
248+
max_entries, true /*delete*/,
249+
has_holes);
228250

229251
/* close all map fds */
230-
for (map_index = 0; map_index < max_entries; map_index++)
252+
for (map_index = 0; map_index < OUTER_MAP_ENTRIES; map_index++)
231253
close(inner_map_fds[map_index]);
232254
close(outer_map_fd);
233255

@@ -237,16 +259,20 @@ static void _map_in_map_batch_ops(enum bpf_map_type outer_map_type,
237259

238260
void test_map_in_map_batch_ops_array(void)
239261
{
240-
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_ARRAY);
262+
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_ARRAY, false);
241263
printf("%s:PASS with inner ARRAY map\n", __func__);
242-
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_HASH);
264+
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_HASH, false);
243265
printf("%s:PASS with inner HASH map\n", __func__);
266+
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_ARRAY, true);
267+
printf("%s:PASS with inner ARRAY map with holes\n", __func__);
268+
_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_HASH, true);
269+
printf("%s:PASS with inner HASH map with holes\n", __func__);
244270
}
245271

246272
void test_map_in_map_batch_ops_hash(void)
247273
{
248-
_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_ARRAY);
274+
_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_ARRAY, false);
249275
printf("%s:PASS with inner ARRAY map\n", __func__);
250-
_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_HASH);
276+
_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_HASH, false);
251277
printf("%s:PASS with inner HASH map\n", __func__);
252278
}

0 commit comments

Comments
 (0)