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