1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <stdio.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <unistd.h>
7 
8 #include <bpf/bpf.h>
9 #include <bpf/libbpf.h>
10 
11 #include <test_maps.h>
12 
13 #define OUTER_MAP_ENTRIES 10
14 
get_map_id_from_fd(int map_fd)15 static __u32 get_map_id_from_fd(int map_fd)
16 {
17 	struct bpf_map_info map_info = {};
18 	uint32_t info_len = sizeof(map_info);
19 	int ret;
20 
21 	ret = bpf_map_get_info_by_fd(map_fd, &map_info, &info_len);
22 	CHECK(ret < 0, "Finding map info failed", "error:%s\n",
23 	      strerror(errno));
24 
25 	return map_info.id;
26 }
27 
28 /* This creates number of OUTER_MAP_ENTRIES maps that will be stored
29  * in outer map and return the created map_fds
30  */
create_inner_maps(enum bpf_map_type map_type,__u32 * inner_map_fds)31 static void create_inner_maps(enum bpf_map_type map_type,
32 			      __u32 *inner_map_fds)
33 {
34 	int map_fd, map_index, ret;
35 	__u32 map_key = 0, map_id;
36 	char map_name[16];
37 
38 	for (map_index = 0; map_index < OUTER_MAP_ENTRIES; map_index++) {
39 		memset(map_name, 0, sizeof(map_name));
40 		snprintf(map_name, sizeof(map_name), "inner_map_fd_%d", map_index);
41 		map_fd = bpf_map_create(map_type, map_name, sizeof(__u32),
42 					sizeof(__u32), 1, NULL);
43 		CHECK(map_fd < 0,
44 		      "inner bpf_map_create() failed",
45 		      "map_type=(%d) map_name(%s), error:%s\n",
46 		      map_type, map_name, strerror(errno));
47 
48 		/* keep track of the inner map fd as it is required
49 		 * to add records in outer map
50 		 */
51 		inner_map_fds[map_index] = map_fd;
52 
53 		/* Add entry into this created map
54 		 * eg: map1 key = 0, value = map1's map id
55 		 *     map2 key = 0, value = map2's map id
56 		 */
57 		map_id = get_map_id_from_fd(map_fd);
58 		ret = bpf_map_update_elem(map_fd, &map_key, &map_id, 0);
59 		CHECK(ret != 0,
60 		      "bpf_map_update_elem failed",
61 		      "map_type=(%d) map_name(%s), error:%s\n",
62 		      map_type, map_name, strerror(errno));
63 	}
64 }
65 
create_outer_map(enum bpf_map_type map_type,__u32 inner_map_fd)66 static int create_outer_map(enum bpf_map_type map_type, __u32 inner_map_fd)
67 {
68 	int outer_map_fd;
69 	LIBBPF_OPTS(bpf_map_create_opts, attr);
70 
71 	attr.inner_map_fd = inner_map_fd;
72 	outer_map_fd = bpf_map_create(map_type, "outer_map", sizeof(__u32),
73 				      sizeof(__u32), OUTER_MAP_ENTRIES,
74 				      &attr);
75 	CHECK(outer_map_fd < 0,
76 	      "outer bpf_map_create()",
77 	      "map_type=(%d), error:%s\n",
78 	      map_type, strerror(errno));
79 
80 	return outer_map_fd;
81 }
82 
validate_fetch_results(int outer_map_fd,__u32 * fetched_keys,__u32 * fetched_values,__u32 max_entries_fetched)83 static void validate_fetch_results(int outer_map_fd,
84 				   __u32 *fetched_keys, __u32 *fetched_values,
85 				   __u32 max_entries_fetched)
86 {
87 	__u32 inner_map_key, inner_map_value;
88 	int inner_map_fd, entry, err;
89 	__u32 outer_map_value;
90 
91 	for (entry = 0; entry < max_entries_fetched; ++entry) {
92 		outer_map_value = fetched_values[entry];
93 		inner_map_fd = bpf_map_get_fd_by_id(outer_map_value);
94 		CHECK(inner_map_fd < 0,
95 		      "Failed to get inner map fd",
96 		      "from id(%d), error=%s\n",
97 		      outer_map_value, strerror(errno));
98 		err = bpf_map_get_next_key(inner_map_fd, NULL, &inner_map_key);
99 		CHECK(err != 0,
100 		      "Failed to get inner map key",
101 		      "error=%s\n", strerror(errno));
102 
103 		err = bpf_map_lookup_elem(inner_map_fd, &inner_map_key,
104 					  &inner_map_value);
105 
106 		close(inner_map_fd);
107 
108 		CHECK(err != 0,
109 		      "Failed to get inner map value",
110 		      "for key(%d), error=%s\n",
111 		      inner_map_key, strerror(errno));
112 
113 		/* Actual value validation */
114 		CHECK(outer_map_value != inner_map_value,
115 		      "Failed to validate inner map value",
116 		      "fetched(%d) and lookedup(%d)!\n",
117 		      outer_map_value, inner_map_value);
118 	}
119 }
120 
fetch_and_validate(int outer_map_fd,struct bpf_map_batch_opts * opts,__u32 batch_size,bool delete_entries,bool has_holes)121 static void fetch_and_validate(int outer_map_fd,
122 			       struct bpf_map_batch_opts *opts,
123 			       __u32 batch_size, bool delete_entries,
124 			       bool has_holes)
125 {
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;
129 	__u32 value_size = sizeof(__u32);
130 
131 	/* Total entries needs to be fetched */
132 	fetched_keys = calloc(max_entries, value_size);
133 	fetched_values = calloc(max_entries, value_size);
134 	CHECK((!fetched_keys || !fetched_values),
135 	      "Memory allocation failed for fetched_keys or fetched_values",
136 	      "error=%s\n", strerror(errno));
137 
138 	/* hash map may not always return full batch */
139 	for (i = 0; i < OUTER_MAP_ENTRIES; i++) {
140 		fetch_count = step_size;
141 		err = delete_entries
142 		      ? bpf_map_lookup_and_delete_batch(outer_map_fd,
143 				      total_fetched ? &batch_key : NULL,
144 				      &batch_key,
145 				      fetched_keys + total_fetched,
146 				      fetched_values + total_fetched,
147 				      &fetch_count, opts)
148 		      : bpf_map_lookup_batch(outer_map_fd,
149 				      total_fetched ? &batch_key : NULL,
150 				      &batch_key,
151 				      fetched_keys + total_fetched,
152 				      fetched_values + total_fetched,
153 				      &fetch_count, opts);
154 
155 		if (err && errno == ENOSPC) {
156 			/* Fetch again with higher batch size */
157 			total_fetched = 0;
158 			step_size += batch_size;
159 			continue;
160 		}
161 
162 		CHECK((err < 0 && (errno != ENOENT)),
163 		      "lookup with steps failed",
164 		      "error: %s\n", strerror(errno));
165 
166 		/* Update the total fetched number */
167 		total_fetched += fetch_count;
168 		if (err)
169 			break;
170 	}
171 
172 	CHECK((total_fetched != max_entries),
173 	      "Unable to fetch expected entries !",
174 	      "total_fetched(%d) and max_entries(%d) error: (%d):%s\n",
175 	      total_fetched, max_entries, errno, strerror(errno));
176 
177 	/* validate the fetched entries */
178 	validate_fetch_results(outer_map_fd, fetched_keys,
179 			       fetched_values, total_fetched);
180 	printf("batch_op(%s) is successful with batch_size(%d)\n",
181 	       delete_entries ? "LOOKUP_AND_DELETE" : "LOOKUP", batch_size);
182 
183 	free(fetched_keys);
184 	free(fetched_values);
185 }
186 
_map_in_map_batch_ops(enum bpf_map_type outer_map_type,enum bpf_map_type inner_map_type,bool has_holes)187 static void _map_in_map_batch_ops(enum bpf_map_type outer_map_type,
188 				  enum bpf_map_type inner_map_type,
189 				  bool has_holes)
190 {
191 	__u32 max_entries = OUTER_MAP_ENTRIES - !!has_holes;
192 	__u32 *outer_map_keys, *inner_map_fds;
193 	LIBBPF_OPTS(bpf_map_batch_opts, opts);
194 	__u32 value_size = sizeof(__u32);
195 	int batch_size[2] = {5, 10};
196 	__u32 map_index, op_index;
197 	int outer_map_fd, ret;
198 
199 	outer_map_keys = calloc(OUTER_MAP_ENTRIES, value_size);
200 	inner_map_fds = calloc(OUTER_MAP_ENTRIES, value_size);
201 	CHECK((!outer_map_keys || !inner_map_fds),
202 	      "Memory allocation failed for outer_map_keys or inner_map_fds",
203 	      "error=%s\n", strerror(errno));
204 
205 	create_inner_maps(inner_map_type, inner_map_fds);
206 
207 	outer_map_fd = create_outer_map(outer_map_type, *inner_map_fds);
208 	/* create outer map keys */
209 	for (map_index = 0; map_index < max_entries; map_index++)
210 		outer_map_keys[map_index] =
211 			((outer_map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
212 			 ? 9 : 1000) - map_index;
213 
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 
232 	/* batch operation - map_update */
233 	ret = bpf_map_update_batch(outer_map_fd, outer_map_keys,
234 				   inner_map_fds, &max_entries, &opts);
235 	CHECK(ret != 0,
236 	      "Failed to update the outer map batch ops",
237 	      "error=%s\n", strerror(errno));
238 
239 	/* batch operation - map_lookup */
240 	for (op_index = 0; op_index < 2; ++op_index)
241 		fetch_and_validate(outer_map_fd, &opts,
242 				   batch_size[op_index], false,
243 				   has_holes);
244 
245 	/* batch operation - map_lookup_delete */
246 	if (outer_map_type == BPF_MAP_TYPE_HASH_OF_MAPS)
247 		fetch_and_validate(outer_map_fd, &opts,
248 				   max_entries, true /*delete*/,
249 				   has_holes);
250 
251 	/* close all map fds */
252 	for (map_index = 0; map_index < OUTER_MAP_ENTRIES; map_index++)
253 		close(inner_map_fds[map_index]);
254 	close(outer_map_fd);
255 
256 	free(inner_map_fds);
257 	free(outer_map_keys);
258 }
259 
test_map_in_map_batch_ops_array(void)260 void test_map_in_map_batch_ops_array(void)
261 {
262 	_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_ARRAY, false);
263 	printf("%s:PASS with inner ARRAY map\n", __func__);
264 	_map_in_map_batch_ops(BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_HASH, false);
265 	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__);
270 }
271 
test_map_in_map_batch_ops_hash(void)272 void test_map_in_map_batch_ops_hash(void)
273 {
274 	_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_ARRAY, false);
275 	printf("%s:PASS with inner ARRAY map\n", __func__);
276 	_map_in_map_batch_ops(BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_HASH, false);
277 	printf("%s:PASS with inner HASH map\n", __func__);
278 }
279