diff options
Diffstat (limited to 'tools/testing/selftests/bpf/test_offload.py')
-rwxr-xr-x | tools/testing/selftests/bpf/test_offload.py | 1085 |
1 files changed, 1085 insertions, 0 deletions
diff --git a/tools/testing/selftests/bpf/test_offload.py b/tools/testing/selftests/bpf/test_offload.py new file mode 100755 index 000000000000..e78aad0a68bb --- /dev/null +++ b/tools/testing/selftests/bpf/test_offload.py | |||
@@ -0,0 +1,1085 @@ | |||
1 | #!/usr/bin/python3 | ||
2 | |||
3 | # Copyright (C) 2017 Netronome Systems, Inc. | ||
4 | # | ||
5 | # This software is licensed under the GNU General License Version 2, | ||
6 | # June 1991 as shown in the file COPYING in the top-level directory of this | ||
7 | # source tree. | ||
8 | # | ||
9 | # THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" | ||
10 | # WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, | ||
11 | # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||
12 | # FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE | ||
13 | # OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME | ||
14 | # THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||
15 | |||
16 | from datetime import datetime | ||
17 | import argparse | ||
18 | import json | ||
19 | import os | ||
20 | import pprint | ||
21 | import random | ||
22 | import string | ||
23 | import struct | ||
24 | import subprocess | ||
25 | import time | ||
26 | |||
27 | logfile = None | ||
28 | log_level = 1 | ||
29 | skip_extack = False | ||
30 | bpf_test_dir = os.path.dirname(os.path.realpath(__file__)) | ||
31 | pp = pprint.PrettyPrinter() | ||
32 | devs = [] # devices we created for clean up | ||
33 | files = [] # files to be removed | ||
34 | netns = [] # net namespaces to be removed | ||
35 | |||
36 | def log_get_sec(level=0): | ||
37 | return "*" * (log_level + level) | ||
38 | |||
39 | def log_level_inc(add=1): | ||
40 | global log_level | ||
41 | log_level += add | ||
42 | |||
43 | def log_level_dec(sub=1): | ||
44 | global log_level | ||
45 | log_level -= sub | ||
46 | |||
47 | def log_level_set(level): | ||
48 | global log_level | ||
49 | log_level = level | ||
50 | |||
51 | def log(header, data, level=None): | ||
52 | """ | ||
53 | Output to an optional log. | ||
54 | """ | ||
55 | if logfile is None: | ||
56 | return | ||
57 | if level is not None: | ||
58 | log_level_set(level) | ||
59 | |||
60 | if not isinstance(data, str): | ||
61 | data = pp.pformat(data) | ||
62 | |||
63 | if len(header): | ||
64 | logfile.write("\n" + log_get_sec() + " ") | ||
65 | logfile.write(header) | ||
66 | if len(header) and len(data.strip()): | ||
67 | logfile.write("\n") | ||
68 | logfile.write(data) | ||
69 | |||
70 | def skip(cond, msg): | ||
71 | if not cond: | ||
72 | return | ||
73 | print("SKIP: " + msg) | ||
74 | log("SKIP: " + msg, "", level=1) | ||
75 | os.sys.exit(0) | ||
76 | |||
77 | def fail(cond, msg): | ||
78 | if not cond: | ||
79 | return | ||
80 | print("FAIL: " + msg) | ||
81 | log("FAIL: " + msg, "", level=1) | ||
82 | os.sys.exit(1) | ||
83 | |||
84 | def start_test(msg): | ||
85 | log(msg, "", level=1) | ||
86 | log_level_inc() | ||
87 | print(msg) | ||
88 | |||
89 | def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True): | ||
90 | """ | ||
91 | Run a command in subprocess and return tuple of (retval, stdout); | ||
92 | optionally return stderr as well as third value. | ||
93 | """ | ||
94 | proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, | ||
95 | stderr=subprocess.PIPE) | ||
96 | if background: | ||
97 | msg = "%s START: %s" % (log_get_sec(1), | ||
98 | datetime.now().strftime("%H:%M:%S.%f")) | ||
99 | log("BKG " + proc.args, msg) | ||
100 | return proc | ||
101 | |||
102 | return cmd_result(proc, include_stderr=include_stderr, fail=fail) | ||
103 | |||
104 | def cmd_result(proc, include_stderr=False, fail=False): | ||
105 | stdout, stderr = proc.communicate() | ||
106 | stdout = stdout.decode("utf-8") | ||
107 | stderr = stderr.decode("utf-8") | ||
108 | proc.stdout.close() | ||
109 | proc.stderr.close() | ||
110 | |||
111 | stderr = "\n" + stderr | ||
112 | if stderr[-1] == "\n": | ||
113 | stderr = stderr[:-1] | ||
114 | |||
115 | sec = log_get_sec(1) | ||
116 | log("CMD " + proc.args, | ||
117 | "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" % | ||
118 | (proc.returncode, sec, stdout, sec, stderr, | ||
119 | sec, datetime.now().strftime("%H:%M:%S.%f"))) | ||
120 | |||
121 | if proc.returncode != 0 and fail: | ||
122 | if len(stderr) > 0 and stderr[-1] == "\n": | ||
123 | stderr = stderr[:-1] | ||
124 | raise Exception("Command failed: %s\n%s" % (proc.args, stderr)) | ||
125 | |||
126 | if include_stderr: | ||
127 | return proc.returncode, stdout, stderr | ||
128 | else: | ||
129 | return proc.returncode, stdout | ||
130 | |||
131 | def rm(f): | ||
132 | cmd("rm -f %s" % (f)) | ||
133 | if f in files: | ||
134 | files.remove(f) | ||
135 | |||
136 | def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False): | ||
137 | params = "" | ||
138 | if JSON: | ||
139 | params += "%s " % (flags["json"]) | ||
140 | |||
141 | if ns != "": | ||
142 | ns = "ip netns exec %s " % (ns) | ||
143 | |||
144 | if include_stderr: | ||
145 | ret, stdout, stderr = cmd(ns + name + " " + params + args, | ||
146 | fail=fail, include_stderr=True) | ||
147 | else: | ||
148 | ret, stdout = cmd(ns + name + " " + params + args, | ||
149 | fail=fail, include_stderr=False) | ||
150 | |||
151 | if JSON and len(stdout.strip()) != 0: | ||
152 | out = json.loads(stdout) | ||
153 | else: | ||
154 | out = stdout | ||
155 | |||
156 | if include_stderr: | ||
157 | return ret, out, stderr | ||
158 | else: | ||
159 | return ret, out | ||
160 | |||
161 | def bpftool(args, JSON=True, ns="", fail=True): | ||
162 | return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail) | ||
163 | |||
164 | def bpftool_prog_list(expected=None, ns=""): | ||
165 | _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True) | ||
166 | if expected is not None: | ||
167 | if len(progs) != expected: | ||
168 | fail(True, "%d BPF programs loaded, expected %d" % | ||
169 | (len(progs), expected)) | ||
170 | return progs | ||
171 | |||
172 | def bpftool_map_list(expected=None, ns=""): | ||
173 | _, maps = bpftool("map show", JSON=True, ns=ns, fail=True) | ||
174 | if expected is not None: | ||
175 | if len(maps) != expected: | ||
176 | fail(True, "%d BPF maps loaded, expected %d" % | ||
177 | (len(maps), expected)) | ||
178 | return maps | ||
179 | |||
180 | def bpftool_prog_list_wait(expected=0, n_retry=20): | ||
181 | for i in range(n_retry): | ||
182 | nprogs = len(bpftool_prog_list()) | ||
183 | if nprogs == expected: | ||
184 | return | ||
185 | time.sleep(0.05) | ||
186 | raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs)) | ||
187 | |||
188 | def bpftool_map_list_wait(expected=0, n_retry=20): | ||
189 | for i in range(n_retry): | ||
190 | nmaps = len(bpftool_map_list()) | ||
191 | if nmaps == expected: | ||
192 | return | ||
193 | time.sleep(0.05) | ||
194 | raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps)) | ||
195 | |||
196 | def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False): | ||
197 | if force: | ||
198 | args = "-force " + args | ||
199 | return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns, | ||
200 | fail=fail, include_stderr=include_stderr) | ||
201 | |||
202 | def tc(args, JSON=True, ns="", fail=True, include_stderr=False): | ||
203 | return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns, | ||
204 | fail=fail, include_stderr=include_stderr) | ||
205 | |||
206 | def ethtool(dev, opt, args, fail=True): | ||
207 | return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail) | ||
208 | |||
209 | def bpf_obj(name, sec=".text", path=bpf_test_dir,): | ||
210 | return "obj %s sec %s" % (os.path.join(path, name), sec) | ||
211 | |||
212 | def bpf_pinned(name): | ||
213 | return "pinned %s" % (name) | ||
214 | |||
215 | def bpf_bytecode(bytecode): | ||
216 | return "bytecode \"%s\"" % (bytecode) | ||
217 | |||
218 | def mknetns(n_retry=10): | ||
219 | for i in range(n_retry): | ||
220 | name = ''.join([random.choice(string.ascii_letters) for i in range(8)]) | ||
221 | ret, _ = ip("netns add %s" % (name), fail=False) | ||
222 | if ret == 0: | ||
223 | netns.append(name) | ||
224 | return name | ||
225 | return None | ||
226 | |||
227 | def int2str(fmt, val): | ||
228 | ret = [] | ||
229 | for b in struct.pack(fmt, val): | ||
230 | ret.append(int(b)) | ||
231 | return " ".join(map(lambda x: str(x), ret)) | ||
232 | |||
233 | def str2int(strtab): | ||
234 | inttab = [] | ||
235 | for i in strtab: | ||
236 | inttab.append(int(i, 16)) | ||
237 | ba = bytearray(inttab) | ||
238 | if len(strtab) == 4: | ||
239 | fmt = "I" | ||
240 | elif len(strtab) == 8: | ||
241 | fmt = "Q" | ||
242 | else: | ||
243 | raise Exception("String array of len %d can't be unpacked to an int" % | ||
244 | (len(strtab))) | ||
245 | return struct.unpack(fmt, ba)[0] | ||
246 | |||
247 | class DebugfsDir: | ||
248 | """ | ||
249 | Class for accessing DebugFS directories as a dictionary. | ||
250 | """ | ||
251 | |||
252 | def __init__(self, path): | ||
253 | self.path = path | ||
254 | self._dict = self._debugfs_dir_read(path) | ||
255 | |||
256 | def __len__(self): | ||
257 | return len(self._dict.keys()) | ||
258 | |||
259 | def __getitem__(self, key): | ||
260 | if type(key) is int: | ||
261 | key = list(self._dict.keys())[key] | ||
262 | return self._dict[key] | ||
263 | |||
264 | def __setitem__(self, key, value): | ||
265 | log("DebugFS set %s = %s" % (key, value), "") | ||
266 | log_level_inc() | ||
267 | |||
268 | cmd("echo '%s' > %s/%s" % (value, self.path, key)) | ||
269 | log_level_dec() | ||
270 | |||
271 | _, out = cmd('cat %s/%s' % (self.path, key)) | ||
272 | self._dict[key] = out.strip() | ||
273 | |||
274 | def _debugfs_dir_read(self, path): | ||
275 | dfs = {} | ||
276 | |||
277 | log("DebugFS state for %s" % (path), "") | ||
278 | log_level_inc(add=2) | ||
279 | |||
280 | _, out = cmd('ls ' + path) | ||
281 | for f in out.split(): | ||
282 | p = os.path.join(path, f) | ||
283 | if os.path.isfile(p): | ||
284 | _, out = cmd('cat %s/%s' % (path, f)) | ||
285 | dfs[f] = out.strip() | ||
286 | elif os.path.isdir(p): | ||
287 | dfs[f] = DebugfsDir(p) | ||
288 | else: | ||
289 | raise Exception("%s is neither file nor directory" % (p)) | ||
290 | |||
291 | log_level_dec() | ||
292 | log("DebugFS state", dfs) | ||
293 | log_level_dec() | ||
294 | |||
295 | return dfs | ||
296 | |||
297 | class NetdevSim: | ||
298 | """ | ||
299 | Class for netdevsim netdevice and its attributes. | ||
300 | """ | ||
301 | |||
302 | def __init__(self): | ||
303 | self.dev = self._netdevsim_create() | ||
304 | devs.append(self) | ||
305 | |||
306 | self.ns = "" | ||
307 | |||
308 | self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname']) | ||
309 | self.dfs_refresh() | ||
310 | |||
311 | def __getitem__(self, key): | ||
312 | return self.dev[key] | ||
313 | |||
314 | def _netdevsim_create(self): | ||
315 | _, old = ip("link show") | ||
316 | ip("link add sim%d type netdevsim") | ||
317 | _, new = ip("link show") | ||
318 | |||
319 | for dev in new: | ||
320 | f = filter(lambda x: x["ifname"] == dev["ifname"], old) | ||
321 | if len(list(f)) == 0: | ||
322 | return dev | ||
323 | |||
324 | raise Exception("failed to create netdevsim device") | ||
325 | |||
326 | def remove(self): | ||
327 | devs.remove(self) | ||
328 | ip("link del dev %s" % (self.dev["ifname"]), ns=self.ns) | ||
329 | |||
330 | def dfs_refresh(self): | ||
331 | self.dfs = DebugfsDir(self.dfs_dir) | ||
332 | return self.dfs | ||
333 | |||
334 | def dfs_num_bound_progs(self): | ||
335 | path = os.path.join(self.dfs_dir, "bpf_bound_progs") | ||
336 | _, progs = cmd('ls %s' % (path)) | ||
337 | return len(progs.split()) | ||
338 | |||
339 | def dfs_get_bound_progs(self, expected): | ||
340 | progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs")) | ||
341 | if expected is not None: | ||
342 | if len(progs) != expected: | ||
343 | fail(True, "%d BPF programs bound, expected %d" % | ||
344 | (len(progs), expected)) | ||
345 | return progs | ||
346 | |||
347 | def wait_for_flush(self, bound=0, total=0, n_retry=20): | ||
348 | for i in range(n_retry): | ||
349 | nbound = self.dfs_num_bound_progs() | ||
350 | nprogs = len(bpftool_prog_list()) | ||
351 | if nbound == bound and nprogs == total: | ||
352 | return | ||
353 | time.sleep(0.05) | ||
354 | raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs)) | ||
355 | |||
356 | def set_ns(self, ns): | ||
357 | name = "1" if ns == "" else ns | ||
358 | ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns) | ||
359 | self.ns = ns | ||
360 | |||
361 | def set_mtu(self, mtu, fail=True): | ||
362 | return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu), | ||
363 | fail=fail) | ||
364 | |||
365 | def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False, | ||
366 | fail=True, include_stderr=False): | ||
367 | if verbose: | ||
368 | bpf += " verbose" | ||
369 | return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf), | ||
370 | force=force, JSON=JSON, | ||
371 | fail=fail, include_stderr=include_stderr) | ||
372 | |||
373 | def unset_xdp(self, mode, force=False, JSON=True, | ||
374 | fail=True, include_stderr=False): | ||
375 | return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode), | ||
376 | force=force, JSON=JSON, | ||
377 | fail=fail, include_stderr=include_stderr) | ||
378 | |||
379 | def ip_link_show(self, xdp): | ||
380 | _, link = ip("link show dev %s" % (self['ifname'])) | ||
381 | if len(link) > 1: | ||
382 | raise Exception("Multiple objects on ip link show") | ||
383 | if len(link) < 1: | ||
384 | return {} | ||
385 | fail(xdp != "xdp" in link, | ||
386 | "XDP program not reporting in iplink (reported %s, expected %s)" % | ||
387 | ("xdp" in link, xdp)) | ||
388 | return link[0] | ||
389 | |||
390 | def tc_add_ingress(self): | ||
391 | tc("qdisc add dev %s ingress" % (self['ifname'])) | ||
392 | |||
393 | def tc_del_ingress(self): | ||
394 | tc("qdisc del dev %s ingress" % (self['ifname'])) | ||
395 | |||
396 | def tc_flush_filters(self, bound=0, total=0): | ||
397 | self.tc_del_ingress() | ||
398 | self.tc_add_ingress() | ||
399 | self.wait_for_flush(bound=bound, total=total) | ||
400 | |||
401 | def tc_show_ingress(self, expected=None): | ||
402 | # No JSON support, oh well... | ||
403 | flags = ["skip_sw", "skip_hw", "in_hw"] | ||
404 | named = ["protocol", "pref", "chain", "handle", "id", "tag"] | ||
405 | |||
406 | args = "-s filter show dev %s ingress" % (self['ifname']) | ||
407 | _, out = tc(args, JSON=False) | ||
408 | |||
409 | filters = [] | ||
410 | lines = out.split('\n') | ||
411 | for line in lines: | ||
412 | words = line.split() | ||
413 | if "handle" not in words: | ||
414 | continue | ||
415 | fltr = {} | ||
416 | for flag in flags: | ||
417 | fltr[flag] = flag in words | ||
418 | for name in named: | ||
419 | try: | ||
420 | idx = words.index(name) | ||
421 | fltr[name] = words[idx + 1] | ||
422 | except ValueError: | ||
423 | pass | ||
424 | filters.append(fltr) | ||
425 | |||
426 | if expected is not None: | ||
427 | fail(len(filters) != expected, | ||
428 | "%d ingress filters loaded, expected %d" % | ||
429 | (len(filters), expected)) | ||
430 | return filters | ||
431 | |||
432 | def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None, | ||
433 | chain=None, cls="", params="", | ||
434 | fail=True, include_stderr=False): | ||
435 | spec = "" | ||
436 | if prio is not None: | ||
437 | spec += " prio %d" % (prio) | ||
438 | if handle: | ||
439 | spec += " handle %s" % (handle) | ||
440 | if chain is not None: | ||
441 | spec += " chain %d" % (chain) | ||
442 | |||
443 | return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\ | ||
444 | .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec, | ||
445 | cls=cls, params=params), | ||
446 | fail=fail, include_stderr=include_stderr) | ||
447 | |||
448 | def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None, | ||
449 | chain=None, da=False, verbose=False, | ||
450 | skip_sw=False, skip_hw=False, | ||
451 | fail=True, include_stderr=False): | ||
452 | cls = "bpf " + bpf | ||
453 | |||
454 | params = "" | ||
455 | if da: | ||
456 | params += " da" | ||
457 | if verbose: | ||
458 | params += " verbose" | ||
459 | if skip_sw: | ||
460 | params += " skip_sw" | ||
461 | if skip_hw: | ||
462 | params += " skip_hw" | ||
463 | |||
464 | return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls, | ||
465 | chain=chain, params=params, | ||
466 | fail=fail, include_stderr=include_stderr) | ||
467 | |||
468 | def set_ethtool_tc_offloads(self, enable, fail=True): | ||
469 | args = "hw-tc-offload %s" % ("on" if enable else "off") | ||
470 | return ethtool(self, "-K", args, fail=fail) | ||
471 | |||
472 | ################################################################################ | ||
473 | def clean_up(): | ||
474 | global files, netns, devs | ||
475 | |||
476 | for dev in devs: | ||
477 | dev.remove() | ||
478 | for f in files: | ||
479 | cmd("rm -f %s" % (f)) | ||
480 | for ns in netns: | ||
481 | cmd("ip netns delete %s" % (ns)) | ||
482 | files = [] | ||
483 | netns = [] | ||
484 | |||
485 | def pin_prog(file_name, idx=0): | ||
486 | progs = bpftool_prog_list(expected=(idx + 1)) | ||
487 | prog = progs[idx] | ||
488 | bpftool("prog pin id %d %s" % (prog["id"], file_name)) | ||
489 | files.append(file_name) | ||
490 | |||
491 | return file_name, bpf_pinned(file_name) | ||
492 | |||
493 | def pin_map(file_name, idx=0, expected=1): | ||
494 | maps = bpftool_map_list(expected=expected) | ||
495 | m = maps[idx] | ||
496 | bpftool("map pin id %d %s" % (m["id"], file_name)) | ||
497 | files.append(file_name) | ||
498 | |||
499 | return file_name, bpf_pinned(file_name) | ||
500 | |||
501 | def check_dev_info_removed(prog_file=None, map_file=None): | ||
502 | bpftool_prog_list(expected=0) | ||
503 | ret, err = bpftool("prog show pin %s" % (prog_file), fail=False) | ||
504 | fail(ret == 0, "Showing prog with removed device did not fail") | ||
505 | fail(err["error"].find("No such device") == -1, | ||
506 | "Showing prog with removed device expected ENODEV, error is %s" % | ||
507 | (err["error"])) | ||
508 | |||
509 | bpftool_map_list(expected=0) | ||
510 | ret, err = bpftool("map show pin %s" % (map_file), fail=False) | ||
511 | fail(ret == 0, "Showing map with removed device did not fail") | ||
512 | fail(err["error"].find("No such device") == -1, | ||
513 | "Showing map with removed device expected ENODEV, error is %s" % | ||
514 | (err["error"])) | ||
515 | |||
516 | def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False): | ||
517 | progs = bpftool_prog_list(expected=1, ns=ns) | ||
518 | prog = progs[0] | ||
519 | |||
520 | fail("dev" not in prog.keys(), "Device parameters not reported") | ||
521 | dev = prog["dev"] | ||
522 | fail("ifindex" not in dev.keys(), "Device parameters not reported") | ||
523 | fail("ns_dev" not in dev.keys(), "Device parameters not reported") | ||
524 | fail("ns_inode" not in dev.keys(), "Device parameters not reported") | ||
525 | |||
526 | if not other_ns: | ||
527 | fail("ifname" not in dev.keys(), "Ifname not reported") | ||
528 | fail(dev["ifname"] != sim["ifname"], | ||
529 | "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"])) | ||
530 | else: | ||
531 | fail("ifname" in dev.keys(), "Ifname is reported for other ns") | ||
532 | |||
533 | maps = bpftool_map_list(expected=2, ns=ns) | ||
534 | for m in maps: | ||
535 | fail("dev" not in m.keys(), "Device parameters not reported") | ||
536 | fail(dev != m["dev"], "Map's device different than program's") | ||
537 | |||
538 | def check_extack(output, reference, args): | ||
539 | if skip_extack: | ||
540 | return | ||
541 | lines = output.split("\n") | ||
542 | comp = len(lines) >= 2 and lines[1] == reference | ||
543 | fail(not comp, "Missing or incorrect netlink extack message") | ||
544 | |||
545 | def check_extack_nsim(output, reference, args): | ||
546 | check_extack(output, "Error: netdevsim: " + reference, args) | ||
547 | |||
548 | def check_no_extack(res, needle): | ||
549 | fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"), | ||
550 | "Found '%s' in command output, leaky extack?" % (needle)) | ||
551 | |||
552 | def check_verifier_log(output, reference): | ||
553 | lines = output.split("\n") | ||
554 | for l in reversed(lines): | ||
555 | if l == reference: | ||
556 | return | ||
557 | fail(True, "Missing or incorrect message from netdevsim in verifier log") | ||
558 | |||
559 | def test_spurios_extack(sim, obj, skip_hw, needle): | ||
560 | res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw, | ||
561 | include_stderr=True) | ||
562 | check_no_extack(res, needle) | ||
563 | res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, | ||
564 | skip_hw=skip_hw, include_stderr=True) | ||
565 | check_no_extack(res, needle) | ||
566 | res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf", | ||
567 | include_stderr=True) | ||
568 | check_no_extack(res, needle) | ||
569 | |||
570 | |||
571 | # Parse command line | ||
572 | parser = argparse.ArgumentParser() | ||
573 | parser.add_argument("--log", help="output verbose log to given file") | ||
574 | args = parser.parse_args() | ||
575 | if args.log: | ||
576 | logfile = open(args.log, 'w+') | ||
577 | logfile.write("# -*-Org-*-") | ||
578 | |||
579 | log("Prepare...", "", level=1) | ||
580 | log_level_inc() | ||
581 | |||
582 | # Check permissions | ||
583 | skip(os.getuid() != 0, "test must be run as root") | ||
584 | |||
585 | # Check tools | ||
586 | ret, progs = bpftool("prog", fail=False) | ||
587 | skip(ret != 0, "bpftool not installed") | ||
588 | # Check no BPF programs are loaded | ||
589 | skip(len(progs) != 0, "BPF programs already loaded on the system") | ||
590 | |||
591 | # Check netdevsim | ||
592 | ret, out = cmd("modprobe netdevsim", fail=False) | ||
593 | skip(ret != 0, "netdevsim module could not be loaded") | ||
594 | |||
595 | # Check debugfs | ||
596 | _, out = cmd("mount") | ||
597 | if out.find("/sys/kernel/debug type debugfs") == -1: | ||
598 | cmd("mount -t debugfs none /sys/kernel/debug") | ||
599 | |||
600 | # Check samples are compiled | ||
601 | samples = ["sample_ret0.o", "sample_map_ret0.o"] | ||
602 | for s in samples: | ||
603 | ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False) | ||
604 | skip(ret != 0, "sample %s/%s not found, please compile it" % | ||
605 | (bpf_test_dir, s)) | ||
606 | |||
607 | # Check if iproute2 is built with libmnl (needed by extack support) | ||
608 | _, _, err = cmd("tc qdisc delete dev lo handle 0", | ||
609 | fail=False, include_stderr=True) | ||
610 | if err.find("Error: Failed to find qdisc with specified handle.") == -1: | ||
611 | print("Warning: no extack message in iproute2 output, libmnl missing?") | ||
612 | log("Warning: no extack message in iproute2 output, libmnl missing?", "") | ||
613 | skip_extack = True | ||
614 | |||
615 | # Check if net namespaces seem to work | ||
616 | ns = mknetns() | ||
617 | skip(ns is None, "Could not create a net namespace") | ||
618 | cmd("ip netns delete %s" % (ns)) | ||
619 | netns = [] | ||
620 | |||
621 | try: | ||
622 | obj = bpf_obj("sample_ret0.o") | ||
623 | bytecode = bpf_bytecode("1,6 0 0 4294967295,") | ||
624 | |||
625 | start_test("Test destruction of generic XDP...") | ||
626 | sim = NetdevSim() | ||
627 | sim.set_xdp(obj, "generic") | ||
628 | sim.remove() | ||
629 | bpftool_prog_list_wait(expected=0) | ||
630 | |||
631 | sim = NetdevSim() | ||
632 | sim.tc_add_ingress() | ||
633 | |||
634 | start_test("Test TC non-offloaded...") | ||
635 | ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False) | ||
636 | fail(ret != 0, "Software TC filter did not load") | ||
637 | |||
638 | start_test("Test TC non-offloaded isn't getting bound...") | ||
639 | ret, _ = sim.cls_bpf_add_filter(obj, fail=False) | ||
640 | fail(ret != 0, "Software TC filter did not load") | ||
641 | sim.dfs_get_bound_progs(expected=0) | ||
642 | |||
643 | sim.tc_flush_filters() | ||
644 | |||
645 | start_test("Test TC offloads are off by default...") | ||
646 | ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True, | ||
647 | fail=False, include_stderr=True) | ||
648 | fail(ret == 0, "TC filter loaded without enabling TC offloads") | ||
649 | check_extack(err, "Error: TC offload is disabled on net device.", args) | ||
650 | sim.wait_for_flush() | ||
651 | |||
652 | sim.set_ethtool_tc_offloads(True) | ||
653 | sim.dfs["bpf_tc_non_bound_accept"] = "Y" | ||
654 | |||
655 | start_test("Test TC offload by default...") | ||
656 | ret, _ = sim.cls_bpf_add_filter(obj, fail=False) | ||
657 | fail(ret != 0, "Software TC filter did not load") | ||
658 | sim.dfs_get_bound_progs(expected=0) | ||
659 | ingress = sim.tc_show_ingress(expected=1) | ||
660 | fltr = ingress[0] | ||
661 | fail(not fltr["in_hw"], "Filter not offloaded by default") | ||
662 | |||
663 | sim.tc_flush_filters() | ||
664 | |||
665 | start_test("Test TC cBPF bytcode tries offload by default...") | ||
666 | ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False) | ||
667 | fail(ret != 0, "Software TC filter did not load") | ||
668 | sim.dfs_get_bound_progs(expected=0) | ||
669 | ingress = sim.tc_show_ingress(expected=1) | ||
670 | fltr = ingress[0] | ||
671 | fail(not fltr["in_hw"], "Bytecode not offloaded by default") | ||
672 | |||
673 | sim.tc_flush_filters() | ||
674 | sim.dfs["bpf_tc_non_bound_accept"] = "N" | ||
675 | |||
676 | start_test("Test TC cBPF unbound bytecode doesn't offload...") | ||
677 | ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True, | ||
678 | fail=False, include_stderr=True) | ||
679 | fail(ret == 0, "TC bytecode loaded for offload") | ||
680 | check_extack_nsim(err, "netdevsim configured to reject unbound programs.", | ||
681 | args) | ||
682 | sim.wait_for_flush() | ||
683 | |||
684 | start_test("Test non-0 chain offload...") | ||
685 | ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1, | ||
686 | skip_sw=True, | ||
687 | fail=False, include_stderr=True) | ||
688 | fail(ret == 0, "Offloaded a filter to chain other than 0") | ||
689 | check_extack(err, "Error: Driver supports only offload of chain 0.", args) | ||
690 | sim.tc_flush_filters() | ||
691 | |||
692 | start_test("Test TC replace...") | ||
693 | sim.cls_bpf_add_filter(obj, prio=1, handle=1) | ||
694 | sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1) | ||
695 | sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") | ||
696 | |||
697 | sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True) | ||
698 | sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True) | ||
699 | sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") | ||
700 | |||
701 | sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True) | ||
702 | sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True) | ||
703 | sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") | ||
704 | |||
705 | start_test("Test TC replace bad flags...") | ||
706 | for i in range(3): | ||
707 | for j in range(3): | ||
708 | ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, | ||
709 | skip_sw=(j == 1), skip_hw=(j == 2), | ||
710 | fail=False) | ||
711 | fail(bool(ret) != bool(j), | ||
712 | "Software TC incorrect load in replace test, iteration %d" % | ||
713 | (j)) | ||
714 | sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") | ||
715 | |||
716 | start_test("Test spurious extack from the driver...") | ||
717 | test_spurios_extack(sim, obj, False, "netdevsim") | ||
718 | test_spurios_extack(sim, obj, True, "netdevsim") | ||
719 | |||
720 | sim.set_ethtool_tc_offloads(False) | ||
721 | |||
722 | test_spurios_extack(sim, obj, False, "TC offload is disabled") | ||
723 | test_spurios_extack(sim, obj, True, "TC offload is disabled") | ||
724 | |||
725 | sim.set_ethtool_tc_offloads(True) | ||
726 | |||
727 | sim.tc_flush_filters() | ||
728 | |||
729 | start_test("Test TC offloads work...") | ||
730 | ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True, | ||
731 | fail=False, include_stderr=True) | ||
732 | fail(ret != 0, "TC filter did not load with TC offloads enabled") | ||
733 | check_verifier_log(err, "[netdevsim] Hello from netdevsim!") | ||
734 | |||
735 | start_test("Test TC offload basics...") | ||
736 | dfs = sim.dfs_get_bound_progs(expected=1) | ||
737 | progs = bpftool_prog_list(expected=1) | ||
738 | ingress = sim.tc_show_ingress(expected=1) | ||
739 | |||
740 | dprog = dfs[0] | ||
741 | prog = progs[0] | ||
742 | fltr = ingress[0] | ||
743 | fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter") | ||
744 | fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter") | ||
745 | fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back") | ||
746 | |||
747 | start_test("Test TC offload is device-bound...") | ||
748 | fail(str(prog["id"]) != fltr["id"], "Program IDs don't match") | ||
749 | fail(prog["tag"] != fltr["tag"], "Program tags don't match") | ||
750 | fail(fltr["id"] != dprog["id"], "Program IDs don't match") | ||
751 | fail(dprog["state"] != "xlated", "Offloaded program state not translated") | ||
752 | fail(dprog["loaded"] != "Y", "Offloaded program is not loaded") | ||
753 | |||
754 | start_test("Test disabling TC offloads is rejected while filters installed...") | ||
755 | ret, _ = sim.set_ethtool_tc_offloads(False, fail=False) | ||
756 | fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...") | ||
757 | |||
758 | start_test("Test qdisc removal frees things...") | ||
759 | sim.tc_flush_filters() | ||
760 | sim.tc_show_ingress(expected=0) | ||
761 | |||
762 | start_test("Test disabling TC offloads is OK without filters...") | ||
763 | ret, _ = sim.set_ethtool_tc_offloads(False, fail=False) | ||
764 | fail(ret != 0, | ||
765 | "Driver refused to disable TC offloads without filters installed...") | ||
766 | |||
767 | sim.set_ethtool_tc_offloads(True) | ||
768 | |||
769 | start_test("Test destroying device gets rid of TC filters...") | ||
770 | sim.cls_bpf_add_filter(obj, skip_sw=True) | ||
771 | sim.remove() | ||
772 | bpftool_prog_list_wait(expected=0) | ||
773 | |||
774 | sim = NetdevSim() | ||
775 | sim.set_ethtool_tc_offloads(True) | ||
776 | |||
777 | start_test("Test destroying device gets rid of XDP...") | ||
778 | sim.set_xdp(obj, "offload") | ||
779 | sim.remove() | ||
780 | bpftool_prog_list_wait(expected=0) | ||
781 | |||
782 | sim = NetdevSim() | ||
783 | sim.set_ethtool_tc_offloads(True) | ||
784 | |||
785 | start_test("Test XDP prog reporting...") | ||
786 | sim.set_xdp(obj, "drv") | ||
787 | ipl = sim.ip_link_show(xdp=True) | ||
788 | progs = bpftool_prog_list(expected=1) | ||
789 | fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], | ||
790 | "Loaded program has wrong ID") | ||
791 | |||
792 | start_test("Test XDP prog replace without force...") | ||
793 | ret, _ = sim.set_xdp(obj, "drv", fail=False) | ||
794 | fail(ret == 0, "Replaced XDP program without -force") | ||
795 | sim.wait_for_flush(total=1) | ||
796 | |||
797 | start_test("Test XDP prog replace with force...") | ||
798 | ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False) | ||
799 | fail(ret != 0, "Could not replace XDP program with -force") | ||
800 | bpftool_prog_list_wait(expected=1) | ||
801 | ipl = sim.ip_link_show(xdp=True) | ||
802 | progs = bpftool_prog_list(expected=1) | ||
803 | fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], | ||
804 | "Loaded program has wrong ID") | ||
805 | fail("dev" in progs[0].keys(), | ||
806 | "Device parameters reported for non-offloaded program") | ||
807 | |||
808 | start_test("Test XDP prog replace with bad flags...") | ||
809 | ret, _, err = sim.set_xdp(obj, "offload", force=True, | ||
810 | fail=False, include_stderr=True) | ||
811 | fail(ret == 0, "Replaced XDP program with a program in different mode") | ||
812 | check_extack_nsim(err, "program loaded with different flags.", args) | ||
813 | ret, _, err = sim.set_xdp(obj, "", force=True, | ||
814 | fail=False, include_stderr=True) | ||
815 | fail(ret == 0, "Replaced XDP program with a program in different mode") | ||
816 | check_extack_nsim(err, "program loaded with different flags.", args) | ||
817 | |||
818 | start_test("Test XDP prog remove with bad flags...") | ||
819 | ret, _, err = sim.unset_xdp("offload", force=True, | ||
820 | fail=False, include_stderr=True) | ||
821 | fail(ret == 0, "Removed program with a bad mode mode") | ||
822 | check_extack_nsim(err, "program loaded with different flags.", args) | ||
823 | ret, _, err = sim.unset_xdp("", force=True, | ||
824 | fail=False, include_stderr=True) | ||
825 | fail(ret == 0, "Removed program with a bad mode mode") | ||
826 | check_extack_nsim(err, "program loaded with different flags.", args) | ||
827 | |||
828 | start_test("Test MTU restrictions...") | ||
829 | ret, _ = sim.set_mtu(9000, fail=False) | ||
830 | fail(ret == 0, | ||
831 | "Driver should refuse to increase MTU to 9000 with XDP loaded...") | ||
832 | sim.unset_xdp("drv") | ||
833 | bpftool_prog_list_wait(expected=0) | ||
834 | sim.set_mtu(9000) | ||
835 | ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True) | ||
836 | fail(ret == 0, "Driver should refuse to load program with MTU of 9000...") | ||
837 | check_extack_nsim(err, "MTU too large w/ XDP enabled.", args) | ||
838 | sim.set_mtu(1500) | ||
839 | |||
840 | sim.wait_for_flush() | ||
841 | start_test("Test XDP offload...") | ||
842 | _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True) | ||
843 | ipl = sim.ip_link_show(xdp=True) | ||
844 | link_xdp = ipl["xdp"]["prog"] | ||
845 | progs = bpftool_prog_list(expected=1) | ||
846 | prog = progs[0] | ||
847 | fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID") | ||
848 | check_verifier_log(err, "[netdevsim] Hello from netdevsim!") | ||
849 | |||
850 | start_test("Test XDP offload is device bound...") | ||
851 | dfs = sim.dfs_get_bound_progs(expected=1) | ||
852 | dprog = dfs[0] | ||
853 | |||
854 | fail(prog["id"] != link_xdp["id"], "Program IDs don't match") | ||
855 | fail(prog["tag"] != link_xdp["tag"], "Program tags don't match") | ||
856 | fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match") | ||
857 | fail(dprog["state"] != "xlated", "Offloaded program state not translated") | ||
858 | fail(dprog["loaded"] != "Y", "Offloaded program is not loaded") | ||
859 | |||
860 | start_test("Test removing XDP program many times...") | ||
861 | sim.unset_xdp("offload") | ||
862 | sim.unset_xdp("offload") | ||
863 | sim.unset_xdp("drv") | ||
864 | sim.unset_xdp("drv") | ||
865 | sim.unset_xdp("") | ||
866 | sim.unset_xdp("") | ||
867 | bpftool_prog_list_wait(expected=0) | ||
868 | |||
869 | start_test("Test attempt to use a program for a wrong device...") | ||
870 | sim2 = NetdevSim() | ||
871 | sim2.set_xdp(obj, "offload") | ||
872 | pin_file, pinned = pin_prog("/sys/fs/bpf/tmp") | ||
873 | |||
874 | ret, _, err = sim.set_xdp(pinned, "offload", | ||
875 | fail=False, include_stderr=True) | ||
876 | fail(ret == 0, "Pinned program loaded for a different device accepted") | ||
877 | check_extack_nsim(err, "program bound to different dev.", args) | ||
878 | sim2.remove() | ||
879 | ret, _, err = sim.set_xdp(pinned, "offload", | ||
880 | fail=False, include_stderr=True) | ||
881 | fail(ret == 0, "Pinned program loaded for a removed device accepted") | ||
882 | check_extack_nsim(err, "xdpoffload of non-bound program.", args) | ||
883 | rm(pin_file) | ||
884 | bpftool_prog_list_wait(expected=0) | ||
885 | |||
886 | start_test("Test mixing of TC and XDP...") | ||
887 | sim.tc_add_ingress() | ||
888 | sim.set_xdp(obj, "offload") | ||
889 | ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True, | ||
890 | fail=False, include_stderr=True) | ||
891 | fail(ret == 0, "Loading TC when XDP active should fail") | ||
892 | check_extack_nsim(err, "driver and netdev offload states mismatch.", args) | ||
893 | sim.unset_xdp("offload") | ||
894 | sim.wait_for_flush() | ||
895 | |||
896 | sim.cls_bpf_add_filter(obj, skip_sw=True) | ||
897 | ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True) | ||
898 | fail(ret == 0, "Loading XDP when TC active should fail") | ||
899 | check_extack_nsim(err, "TC program is already loaded.", args) | ||
900 | |||
901 | start_test("Test binding TC from pinned...") | ||
902 | pin_file, pinned = pin_prog("/sys/fs/bpf/tmp") | ||
903 | sim.tc_flush_filters(bound=1, total=1) | ||
904 | sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True) | ||
905 | sim.tc_flush_filters(bound=1, total=1) | ||
906 | |||
907 | start_test("Test binding XDP from pinned...") | ||
908 | sim.set_xdp(obj, "offload") | ||
909 | pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1) | ||
910 | |||
911 | sim.set_xdp(pinned, "offload", force=True) | ||
912 | sim.unset_xdp("offload") | ||
913 | sim.set_xdp(pinned, "offload", force=True) | ||
914 | sim.unset_xdp("offload") | ||
915 | |||
916 | start_test("Test offload of wrong type fails...") | ||
917 | ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False) | ||
918 | fail(ret == 0, "Managed to attach XDP program to TC") | ||
919 | |||
920 | start_test("Test asking for TC offload of two filters...") | ||
921 | sim.cls_bpf_add_filter(obj, da=True, skip_sw=True) | ||
922 | ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True, | ||
923 | fail=False, include_stderr=True) | ||
924 | fail(ret == 0, "Managed to offload two TC filters at the same time") | ||
925 | check_extack_nsim(err, "driver and netdev offload states mismatch.", args) | ||
926 | |||
927 | sim.tc_flush_filters(bound=2, total=2) | ||
928 | |||
929 | start_test("Test if netdev removal waits for translation...") | ||
930 | delay_msec = 500 | ||
931 | sim.dfs["bpf_bind_verifier_delay"] = delay_msec | ||
932 | start = time.time() | ||
933 | cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \ | ||
934 | (sim['ifname'], obj) | ||
935 | tc_proc = cmd(cmd_line, background=True, fail=False) | ||
936 | # Wait for the verifier to start | ||
937 | while sim.dfs_num_bound_progs() <= 2: | ||
938 | pass | ||
939 | sim.remove() | ||
940 | end = time.time() | ||
941 | ret, _ = cmd_result(tc_proc, fail=False) | ||
942 | time_diff = end - start | ||
943 | log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff)) | ||
944 | |||
945 | fail(ret == 0, "Managed to load TC filter on a unregistering device") | ||
946 | delay_sec = delay_msec * 0.001 | ||
947 | fail(time_diff < delay_sec, "Removal process took %s, expected %s" % | ||
948 | (time_diff, delay_sec)) | ||
949 | |||
950 | # Remove all pinned files and reinstantiate the netdev | ||
951 | clean_up() | ||
952 | bpftool_prog_list_wait(expected=0) | ||
953 | |||
954 | sim = NetdevSim() | ||
955 | map_obj = bpf_obj("sample_map_ret0.o") | ||
956 | start_test("Test loading program with maps...") | ||
957 | sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON | ||
958 | |||
959 | start_test("Test bpftool bound info reporting (own ns)...") | ||
960 | check_dev_info(False, "") | ||
961 | |||
962 | start_test("Test bpftool bound info reporting (other ns)...") | ||
963 | ns = mknetns() | ||
964 | sim.set_ns(ns) | ||
965 | check_dev_info(True, "") | ||
966 | |||
967 | start_test("Test bpftool bound info reporting (remote ns)...") | ||
968 | check_dev_info(False, ns) | ||
969 | |||
970 | start_test("Test bpftool bound info reporting (back to own ns)...") | ||
971 | sim.set_ns("") | ||
972 | check_dev_info(False, "") | ||
973 | |||
974 | prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog") | ||
975 | map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2) | ||
976 | sim.remove() | ||
977 | |||
978 | start_test("Test bpftool bound info reporting (removed dev)...") | ||
979 | check_dev_info_removed(prog_file=prog_file, map_file=map_file) | ||
980 | |||
981 | # Remove all pinned files and reinstantiate the netdev | ||
982 | clean_up() | ||
983 | bpftool_prog_list_wait(expected=0) | ||
984 | |||
985 | sim = NetdevSim() | ||
986 | |||
987 | start_test("Test map update (no flags)...") | ||
988 | sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON | ||
989 | maps = bpftool_map_list(expected=2) | ||
990 | array = maps[0] if maps[0]["type"] == "array" else maps[1] | ||
991 | htab = maps[0] if maps[0]["type"] == "hash" else maps[1] | ||
992 | for m in maps: | ||
993 | for i in range(2): | ||
994 | bpftool("map update id %d key %s value %s" % | ||
995 | (m["id"], int2str("I", i), int2str("Q", i * 3))) | ||
996 | |||
997 | for m in maps: | ||
998 | ret, _ = bpftool("map update id %d key %s value %s" % | ||
999 | (m["id"], int2str("I", 3), int2str("Q", 3 * 3)), | ||
1000 | fail=False) | ||
1001 | fail(ret == 0, "added too many entries") | ||
1002 | |||
1003 | start_test("Test map update (exists)...") | ||
1004 | for m in maps: | ||
1005 | for i in range(2): | ||
1006 | bpftool("map update id %d key %s value %s exist" % | ||
1007 | (m["id"], int2str("I", i), int2str("Q", i * 3))) | ||
1008 | |||
1009 | for m in maps: | ||
1010 | ret, err = bpftool("map update id %d key %s value %s exist" % | ||
1011 | (m["id"], int2str("I", 3), int2str("Q", 3 * 3)), | ||
1012 | fail=False) | ||
1013 | fail(ret == 0, "updated non-existing key") | ||
1014 | fail(err["error"].find("No such file or directory") == -1, | ||
1015 | "expected ENOENT, error is '%s'" % (err["error"])) | ||
1016 | |||
1017 | start_test("Test map update (noexist)...") | ||
1018 | for m in maps: | ||
1019 | for i in range(2): | ||
1020 | ret, err = bpftool("map update id %d key %s value %s noexist" % | ||
1021 | (m["id"], int2str("I", i), int2str("Q", i * 3)), | ||
1022 | fail=False) | ||
1023 | fail(ret == 0, "updated existing key") | ||
1024 | fail(err["error"].find("File exists") == -1, | ||
1025 | "expected EEXIST, error is '%s'" % (err["error"])) | ||
1026 | |||
1027 | start_test("Test map dump...") | ||
1028 | for m in maps: | ||
1029 | _, entries = bpftool("map dump id %d" % (m["id"])) | ||
1030 | for i in range(2): | ||
1031 | key = str2int(entries[i]["key"]) | ||
1032 | fail(key != i, "expected key %d, got %d" % (key, i)) | ||
1033 | val = str2int(entries[i]["value"]) | ||
1034 | fail(val != i * 3, "expected value %d, got %d" % (val, i * 3)) | ||
1035 | |||
1036 | start_test("Test map getnext...") | ||
1037 | for m in maps: | ||
1038 | _, entry = bpftool("map getnext id %d" % (m["id"])) | ||
1039 | key = str2int(entry["next_key"]) | ||
1040 | fail(key != 0, "next key %d, expected %d" % (key, 0)) | ||
1041 | _, entry = bpftool("map getnext id %d key %s" % | ||
1042 | (m["id"], int2str("I", 0))) | ||
1043 | key = str2int(entry["next_key"]) | ||
1044 | fail(key != 1, "next key %d, expected %d" % (key, 1)) | ||
1045 | ret, err = bpftool("map getnext id %d key %s" % | ||
1046 | (m["id"], int2str("I", 1)), fail=False) | ||
1047 | fail(ret == 0, "got next key past the end of map") | ||
1048 | fail(err["error"].find("No such file or directory") == -1, | ||
1049 | "expected ENOENT, error is '%s'" % (err["error"])) | ||
1050 | |||
1051 | start_test("Test map delete (htab)...") | ||
1052 | for i in range(2): | ||
1053 | bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i))) | ||
1054 | |||
1055 | start_test("Test map delete (array)...") | ||
1056 | for i in range(2): | ||
1057 | ret, err = bpftool("map delete id %d key %s" % | ||
1058 | (htab["id"], int2str("I", i)), fail=False) | ||
1059 | fail(ret == 0, "removed entry from an array") | ||
1060 | fail(err["error"].find("No such file or directory") == -1, | ||
1061 | "expected ENOENT, error is '%s'" % (err["error"])) | ||
1062 | |||
1063 | start_test("Test map remove...") | ||
1064 | sim.unset_xdp("offload") | ||
1065 | bpftool_map_list_wait(expected=0) | ||
1066 | sim.remove() | ||
1067 | |||
1068 | sim = NetdevSim() | ||
1069 | sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON | ||
1070 | sim.remove() | ||
1071 | bpftool_map_list_wait(expected=0) | ||
1072 | |||
1073 | start_test("Test map creation fail path...") | ||
1074 | sim = NetdevSim() | ||
1075 | sim.dfs["bpf_map_accept"] = "N" | ||
1076 | ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False) | ||
1077 | fail(ret == 0, | ||
1078 | "netdevsim didn't refuse to create a map with offload disabled") | ||
1079 | |||
1080 | print("%s: OK" % (os.path.basename(__file__))) | ||
1081 | |||
1082 | finally: | ||
1083 | log("Clean up...", "", level=1) | ||
1084 | log_level_inc() | ||
1085 | clean_up() | ||