3 # Copyright (C) 2017 Netronome Systems, Inc.
4 # Copyright (c) 2019 Mellanox Technologies. All rights reserved
6 # This software is licensed under the GNU General License Version 2,
7 # June 1991 as shown in the file COPYING in the top-level directory of this
10 # THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
11 # WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
12 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
13 # FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
14 # OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
15 # THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
17 from datetime import datetime
35 bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
36 pp = pprint.PrettyPrinter()
37 devs = [] # devices we created for clean up
38 files = [] # files to be removed
39 netns = [] # net namespaces to be removed
41 def log_get_sec(level=0):
42 return "*" * (log_level + level)
44 def log_level_inc(add=1):
48 def log_level_dec(sub=1):
52 def log_level_set(level):
56 def log(header, data, level=None):
58 Output to an optional log.
65 if not isinstance(data, str):
66 data = pp.pformat(data)
69 logfile.write("\n" + log_get_sec() + " ")
71 if len(header) and len(data.strip()):
79 log("SKIP: " + msg, "", level=1)
86 tb = "".join(traceback.extract_stack().format())
88 log("FAIL: " + msg, tb, level=1)
96 def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
98 Run a command in subprocess and return tuple of (retval, stdout);
99 optionally return stderr as well as third value.
101 proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
102 stderr=subprocess.PIPE)
104 msg = "%s START: %s" % (log_get_sec(1),
105 datetime.now().strftime("%H:%M:%S.%f"))
106 log("BKG " + proc.args, msg)
109 return cmd_result(proc, include_stderr=include_stderr, fail=fail)
111 def cmd_result(proc, include_stderr=False, fail=False):
112 stdout, stderr = proc.communicate()
113 stdout = stdout.decode("utf-8")
114 stderr = stderr.decode("utf-8")
118 stderr = "\n" + stderr
119 if stderr[-1] == "\n":
123 log("CMD " + proc.args,
124 "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
125 (proc.returncode, sec, stdout, sec, stderr,
126 sec, datetime.now().strftime("%H:%M:%S.%f")))
128 if proc.returncode != 0 and fail:
129 if len(stderr) > 0 and stderr[-1] == "\n":
131 raise Exception("Command failed: %s\n%s" % (proc.args, stderr))
134 return proc.returncode, stdout, stderr
136 return proc.returncode, stdout
139 cmd("rm -f %s" % (f))
143 def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False):
146 params += "%s " % (flags["json"])
149 ns = "ip netns exec %s " % (ns)
152 ret, stdout, stderr = cmd(ns + name + " " + params + args,
153 fail=fail, include_stderr=True)
155 ret, stdout = cmd(ns + name + " " + params + args,
156 fail=fail, include_stderr=False)
158 if JSON and len(stdout.strip()) != 0:
159 out = json.loads(stdout)
164 return ret, out, stderr
168 def bpftool(args, JSON=True, ns="", fail=True, include_stderr=False):
169 return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns,
170 fail=fail, include_stderr=include_stderr)
172 def bpftool_prog_list(expected=None, ns=""):
173 _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
174 # Remove the base progs
178 if expected is not None:
179 if len(progs) != expected:
180 fail(True, "%d BPF programs loaded, expected %d" %
181 (len(progs), expected))
184 def bpftool_map_list(expected=None, ns=""):
185 _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
186 # Remove the base maps
187 maps = [m for m in maps if m not in base_maps and m.get('name') and m.get('name') not in base_map_names]
188 if expected is not None:
189 if len(maps) != expected:
190 fail(True, "%d BPF maps loaded, expected %d" %
191 (len(maps), expected))
194 def bpftool_prog_list_wait(expected=0, n_retry=20):
195 for i in range(n_retry):
196 nprogs = len(bpftool_prog_list())
197 if nprogs == expected:
200 raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
202 def bpftool_map_list_wait(expected=0, n_retry=20):
203 for i in range(n_retry):
204 nmaps = len(bpftool_map_list())
205 if nmaps == expected:
208 raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))
210 def bpftool_prog_load(sample, file_name, maps=[], prog_type="xdp", dev=None,
211 fail=True, include_stderr=False):
212 args = "prog load %s %s" % (os.path.join(bpf_test_dir, sample), file_name)
213 if prog_type is not None:
214 args += " type " + prog_type
216 args += " dev " + dev
218 args += " map " + " map ".join(maps)
220 res = bpftool(args, fail=fail, include_stderr=include_stderr)
222 files.append(file_name)
225 def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False):
227 args = "-force " + args
228 return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns,
229 fail=fail, include_stderr=include_stderr)
231 def tc(args, JSON=True, ns="", fail=True, include_stderr=False):
232 return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns,
233 fail=fail, include_stderr=include_stderr)
235 def ethtool(dev, opt, args, fail=True):
236 return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)
238 def bpf_obj(name, sec=".text", path=bpf_test_dir,):
239 return "obj %s sec %s" % (os.path.join(path, name), sec)
241 def bpf_pinned(name):
242 return "pinned %s" % (name)
244 def bpf_bytecode(bytecode):
245 return "bytecode \"%s\"" % (bytecode)
247 def mknetns(n_retry=10):
248 for i in range(n_retry):
249 name = ''.join([random.choice(string.ascii_letters) for i in range(8)])
250 ret, _ = ip("netns add %s" % (name), fail=False)
256 def int2str(fmt, val):
258 for b in struct.pack(fmt, val):
260 return " ".join(map(lambda x: str(x), ret))
265 inttab.append(int(i, 16))
266 ba = bytearray(inttab)
269 elif len(strtab) == 8:
272 raise Exception("String array of len %d can't be unpacked to an int" %
274 return struct.unpack(fmt, ba)[0]
278 Class for accessing DebugFS directories as a dictionary.
281 def __init__(self, path):
283 self._dict = self._debugfs_dir_read(path)
286 return len(self._dict.keys())
288 def __getitem__(self, key):
290 key = list(self._dict.keys())[key]
291 return self._dict[key]
293 def __setitem__(self, key, value):
294 log("DebugFS set %s = %s" % (key, value), "")
297 cmd("echo '%s' > %s/%s" % (value, self.path, key))
300 _, out = cmd('cat %s/%s' % (self.path, key))
301 self._dict[key] = out.strip()
303 def _debugfs_dir_read(self, path):
306 log("DebugFS state for %s" % (path), "")
309 _, out = cmd('ls ' + path)
310 for f in out.split():
314 p = os.path.join(path, f)
315 if not os.stat(p).st_mode & stat.S_IRUSR:
318 if os.path.isfile(p):
319 # We need to init trap_flow_action_cookie before read it
320 if f == "trap_flow_action_cookie":
321 cmd('echo deadbeef > %s/%s' % (path, f))
322 _, out = cmd('cat %s/%s' % (path, f))
324 elif os.path.isdir(p):
325 dfs[f] = DebugfsDir(p)
327 raise Exception("%s is neither file nor directory" % (p))
330 log("DebugFS state", dfs)
337 Class for netdevsim bus device and its attributes.
340 def ctrl_write(path, val):
341 fullpath = os.path.join("/sys/bus/netdevsim/", path)
343 with open(fullpath, "w") as f:
346 log("WRITE %s: %r" % (fullpath, val), -e.errno)
348 log("WRITE %s: %r" % (fullpath, val), 0)
350 def __init__(self, port_count=1):
354 self.ctrl_write("new_device", "%u %u" % (addr, port_count))
356 if e.errno == errno.ENOSPC:
363 # As probe of netdevsim device might happen from a workqueue,
364 # so wait here until all netdevs appear.
365 self.wait_for_netdevs(port_count)
367 ret, out = cmd("udevadm settle", fail=False)
369 raise Exception("udevadm settle failed")
370 ifnames = self.get_ifnames()
373 self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
376 for port_index in range(port_count):
377 self.nsims.append(NetdevSim(self, port_index, ifnames[port_index]))
379 def get_ifnames(self):
381 listdir = os.listdir("/sys/bus/netdevsim/devices/netdevsim%u/net/" % self.addr)
382 for ifname in listdir:
383 ifnames.append(ifname)
387 def wait_for_netdevs(self, port_count):
389 timeout_start = time.time()
393 ifnames = self.get_ifnames()
394 except FileNotFoundError as e:
396 if len(ifnames) == port_count:
398 if time.time() < timeout_start + timeout:
400 raise Exception("netdevices did not appear within timeout")
402 def dfs_num_bound_progs(self):
403 path = os.path.join(self.dfs_dir, "bpf_bound_progs")
404 _, progs = cmd('ls %s' % (path))
405 return len(progs.split())
407 def dfs_get_bound_progs(self, expected):
408 progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
409 if expected is not None:
410 if len(progs) != expected:
411 fail(True, "%d BPF programs bound, expected %d" %
412 (len(progs), expected))
416 self.ctrl_write("del_device", "%u" % (self.addr, ))
419 def remove_nsim(self, nsim):
420 self.nsims.remove(nsim)
421 self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
422 "%u" % (nsim.port_index, ))
426 Class for netdevsim netdevice and its attributes.
429 def __init__(self, nsimdev, port_index, ifname):
430 # In case udev renamed the netdev to according to new schema,
431 # check if the name matches the port_index.
432 nsimnamere = re.compile("eni\d+np(\d+)")
433 match = nsimnamere.match(ifname)
434 if match and int(match.groups()[0]) != port_index + 1:
435 raise Exception("netdevice name mismatches the expected one")
437 self.nsimdev = nsimdev
438 self.port_index = port_index
440 self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
442 _, [self.dev] = ip("link show dev %s" % ifname)
444 def __getitem__(self, key):
448 self.nsimdev.remove_nsim(self)
450 def dfs_refresh(self):
451 self.dfs = DebugfsDir(self.dfs_dir)
454 def dfs_read(self, f):
455 path = os.path.join(self.dfs_dir, f)
456 _, data = cmd('cat %s' % (path))
459 def wait_for_flush(self, bound=0, total=0, n_retry=20):
460 for i in range(n_retry):
461 nbound = self.nsimdev.dfs_num_bound_progs()
462 nprogs = len(bpftool_prog_list())
463 if nbound == bound and nprogs == total:
466 raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
468 def set_ns(self, ns):
469 name = "1" if ns == "" else ns
470 ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
473 def set_mtu(self, mtu, fail=True):
474 return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
477 def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
478 fail=True, include_stderr=False):
481 return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
482 force=force, JSON=JSON,
483 fail=fail, include_stderr=include_stderr)
485 def unset_xdp(self, mode, force=False, JSON=True,
486 fail=True, include_stderr=False):
487 return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
488 force=force, JSON=JSON,
489 fail=fail, include_stderr=include_stderr)
491 def ip_link_show(self, xdp):
492 _, link = ip("link show dev %s" % (self['ifname']))
494 raise Exception("Multiple objects on ip link show")
497 fail(xdp != "xdp" in link,
498 "XDP program not reporting in iplink (reported %s, expected %s)" %
499 ("xdp" in link, xdp))
502 def tc_add_ingress(self):
503 tc("qdisc add dev %s ingress" % (self['ifname']))
505 def tc_del_ingress(self):
506 tc("qdisc del dev %s ingress" % (self['ifname']))
508 def tc_flush_filters(self, bound=0, total=0):
509 self.tc_del_ingress()
510 self.tc_add_ingress()
511 self.wait_for_flush(bound=bound, total=total)
513 def tc_show_ingress(self, expected=None):
514 # No JSON support, oh well...
515 flags = ["skip_sw", "skip_hw", "in_hw"]
516 named = ["protocol", "pref", "chain", "handle", "id", "tag"]
518 args = "-s filter show dev %s ingress" % (self['ifname'])
519 _, out = tc(args, JSON=False)
522 lines = out.split('\n')
525 if "handle" not in words:
529 fltr[flag] = flag in words
532 idx = words.index(name)
533 fltr[name] = words[idx + 1]
538 if expected is not None:
539 fail(len(filters) != expected,
540 "%d ingress filters loaded, expected %d" %
541 (len(filters), expected))
544 def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None,
545 chain=None, cls="", params="",
546 fail=True, include_stderr=False):
549 spec += " prio %d" % (prio)
551 spec += " handle %s" % (handle)
552 if chain is not None:
553 spec += " chain %d" % (chain)
555 return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\
556 .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec,
557 cls=cls, params=params),
558 fail=fail, include_stderr=include_stderr)
560 def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None,
561 chain=None, da=False, verbose=False,
562 skip_sw=False, skip_hw=False,
563 fail=True, include_stderr=False):
576 return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls,
577 chain=chain, params=params,
578 fail=fail, include_stderr=include_stderr)
580 def set_ethtool_tc_offloads(self, enable, fail=True):
581 args = "hw-tc-offload %s" % ("on" if enable else "off")
582 return ethtool(self, "-K", args, fail=fail)
584 ################################################################################
586 global files, netns, devs
591 cmd("rm -f %s" % (f))
593 cmd("ip netns delete %s" % (ns))
597 def pin_prog(file_name, idx=0):
598 progs = bpftool_prog_list(expected=(idx + 1))
600 bpftool("prog pin id %d %s" % (prog["id"], file_name))
601 files.append(file_name)
603 return file_name, bpf_pinned(file_name)
605 def pin_map(file_name, idx=0, expected=1):
606 maps = bpftool_map_list(expected=expected)
608 bpftool("map pin id %d %s" % (m["id"], file_name))
609 files.append(file_name)
611 return file_name, bpf_pinned(file_name)
613 def check_dev_info_removed(prog_file=None, map_file=None):
614 bpftool_prog_list(expected=0)
615 ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
616 fail(ret == 0, "Showing prog with removed device did not fail")
617 fail(err["error"].find("No such device") == -1,
618 "Showing prog with removed device expected ENODEV, error is %s" %
621 bpftool_map_list(expected=0)
622 ret, err = bpftool("map show pin %s" % (map_file), fail=False)
623 fail(ret == 0, "Showing map with removed device did not fail")
624 fail(err["error"].find("No such device") == -1,
625 "Showing map with removed device expected ENODEV, error is %s" %
628 def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
629 progs = bpftool_prog_list(expected=1, ns=ns)
632 fail("dev" not in prog.keys(), "Device parameters not reported")
634 fail("ifindex" not in dev.keys(), "Device parameters not reported")
635 fail("ns_dev" not in dev.keys(), "Device parameters not reported")
636 fail("ns_inode" not in dev.keys(), "Device parameters not reported")
639 fail("ifname" not in dev.keys(), "Ifname not reported")
640 fail(dev["ifname"] != sim["ifname"],
641 "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
643 fail("ifname" in dev.keys(), "Ifname is reported for other ns")
645 maps = bpftool_map_list(expected=2, ns=ns)
647 fail("dev" not in m.keys(), "Device parameters not reported")
648 fail(dev != m["dev"], "Map's device different than program's")
650 def check_extack(output, reference, args):
653 lines = output.split("\n")
654 comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference
655 fail(not comp, "Missing or incorrect netlink extack message")
657 def check_extack_nsim(output, reference, args):
658 check_extack(output, "netdevsim: " + reference, args)
660 def check_no_extack(res, needle):
661 fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"),
662 "Found '%s' in command output, leaky extack?" % (needle))
664 def check_verifier_log(output, reference):
665 lines = output.split("\n")
666 for l in reversed(lines):
669 fail(True, "Missing or incorrect message from netdevsim in verifier log")
671 def check_multi_basic(two_xdps):
672 fail(two_xdps["mode"] != 4, "Bad mode reported with multiple programs")
673 fail("prog" in two_xdps, "Base program reported in multi program mode")
674 fail(len(two_xdps["attached"]) != 2,
675 "Wrong attached program count with two programs")
676 fail(two_xdps["attached"][0]["prog"]["id"] ==
677 two_xdps["attached"][1]["prog"]["id"],
678 "Offloaded and other programs have the same id")
680 def test_spurios_extack(sim, obj, skip_hw, needle):
681 res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw,
683 check_no_extack(res, needle)
684 res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
685 skip_hw=skip_hw, include_stderr=True)
686 check_no_extack(res, needle)
687 res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf",
689 check_no_extack(res, needle)
691 def test_multi_prog(simdev, sim, obj, modename, modeid):
692 start_test("Test multi-attachment XDP - %s + offload..." %
693 (modename or "default", ))
694 sim.set_xdp(obj, "offload")
695 xdp = sim.ip_link_show(xdp=True)["xdp"]
696 offloaded = sim.dfs_read("bpf_offloaded_id")
697 fail("prog" not in xdp, "Base program not reported in single program mode")
698 fail(len(xdp["attached"]) != 1,
699 "Wrong attached program count with one program")
701 sim.set_xdp(obj, modename)
702 two_xdps = sim.ip_link_show(xdp=True)["xdp"]
704 fail(xdp["attached"][0] not in two_xdps["attached"],
705 "Offload program not reported after other activated")
706 check_multi_basic(two_xdps)
708 offloaded2 = sim.dfs_read("bpf_offloaded_id")
709 fail(offloaded != offloaded2,
710 "Offload ID changed after loading other program")
712 start_test("Test multi-attachment XDP - replace...")
713 ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
714 fail(ret == 0, "Replaced one of programs without -force")
715 check_extack(err, "XDP program already attached.", args)
717 start_test("Test multi-attachment XDP - remove without mode...")
718 ret, _, err = sim.unset_xdp("", force=True,
719 fail=False, include_stderr=True)
720 fail(ret == 0, "Removed program without a mode flag")
721 check_extack(err, "More than one program loaded, unset mode is ambiguous.", args)
723 sim.unset_xdp("offload")
724 xdp = sim.ip_link_show(xdp=True)["xdp"]
725 offloaded = sim.dfs_read("bpf_offloaded_id")
727 fail(xdp["mode"] != modeid, "Bad mode reported after multiple programs")
728 fail("prog" not in xdp,
729 "Base program not reported after multi program mode")
730 fail(xdp["attached"][0] not in two_xdps["attached"],
731 "Offload program not reported after other activated")
732 fail(len(xdp["attached"]) != 1,
733 "Wrong attached program count with remaining programs")
734 fail(offloaded != "0", "Offload ID reported with only other program left")
736 start_test("Test multi-attachment XDP - reattach...")
737 sim.set_xdp(obj, "offload")
738 two_xdps = sim.ip_link_show(xdp=True)["xdp"]
740 fail(xdp["attached"][0] not in two_xdps["attached"],
741 "Other program not reported after offload activated")
742 check_multi_basic(two_xdps)
744 start_test("Test multi-attachment XDP - device remove...")
747 simdev = NetdevSimDev()
749 sim.set_ethtool_tc_offloads(True)
753 parser = argparse.ArgumentParser()
754 parser.add_argument("--log", help="output verbose log to given file")
755 args = parser.parse_args()
757 logfile = open(args.log, 'w+')
758 logfile.write("# -*-Org-*-")
760 log("Prepare...", "", level=1)
764 skip(os.getuid() != 0, "test must be run as root")
767 ret, progs = bpftool("prog", fail=False)
768 skip(ret != 0, "bpftool not installed")
770 _, base_maps = bpftool("map")
772 'pid_iter.rodata', # created on each bpftool invocation
773 'libbpf_det_bind', # created on each bpftool invocation
777 if not os.path.isdir("/sys/bus/netdevsim/"):
778 ret, out = cmd("modprobe netdevsim", fail=False)
779 skip(ret != 0, "netdevsim module could not be loaded")
782 _, out = cmd("mount")
783 if out.find("/sys/kernel/debug type debugfs") == -1:
784 cmd("mount -t debugfs none /sys/kernel/debug")
786 # Check samples are compiled
787 samples = ["sample_ret0.bpf.o", "sample_map_ret0.bpf.o"]
789 ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
790 skip(ret != 0, "sample %s/%s not found, please compile it" %
793 # Check if iproute2 is built with libmnl (needed by extack support)
794 _, _, err = cmd("tc qdisc delete dev lo handle 0",
795 fail=False, include_stderr=True)
796 if err.find("Error: Failed to find qdisc with specified handle.") == -1:
797 print("Warning: no extack message in iproute2 output, libmnl missing?")
798 log("Warning: no extack message in iproute2 output, libmnl missing?", "")
801 # Check if net namespaces seem to work
803 skip(ns is None, "Could not create a net namespace")
804 cmd("ip netns delete %s" % (ns))
808 obj = bpf_obj("sample_ret0.bpf.o")
809 bytecode = bpf_bytecode("1,6 0 0 4294967295,")
811 start_test("Test destruction of generic XDP...")
812 simdev = NetdevSimDev()
814 sim.set_xdp(obj, "generic")
816 bpftool_prog_list_wait(expected=0)
818 simdev = NetdevSimDev()
822 start_test("Test TC non-offloaded...")
823 ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
824 fail(ret != 0, "Software TC filter did not load")
826 start_test("Test TC non-offloaded isn't getting bound...")
827 ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
828 fail(ret != 0, "Software TC filter did not load")
829 simdev.dfs_get_bound_progs(expected=0)
831 sim.tc_flush_filters()
833 start_test("Test TC offloads are off by default...")
834 ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
835 fail=False, include_stderr=True)
836 fail(ret == 0, "TC filter loaded without enabling TC offloads")
837 check_extack(err, "TC offload is disabled on net device.", args)
840 sim.set_ethtool_tc_offloads(True)
841 sim.dfs["bpf_tc_non_bound_accept"] = "Y"
843 start_test("Test TC offload by default...")
844 ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
845 fail(ret != 0, "Software TC filter did not load")
846 simdev.dfs_get_bound_progs(expected=0)
847 ingress = sim.tc_show_ingress(expected=1)
849 fail(not fltr["in_hw"], "Filter not offloaded by default")
851 sim.tc_flush_filters()
853 start_test("Test TC cBPF bytcode tries offload by default...")
854 ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
855 fail(ret != 0, "Software TC filter did not load")
856 simdev.dfs_get_bound_progs(expected=0)
857 ingress = sim.tc_show_ingress(expected=1)
859 fail(not fltr["in_hw"], "Bytecode not offloaded by default")
861 sim.tc_flush_filters()
862 sim.dfs["bpf_tc_non_bound_accept"] = "N"
864 start_test("Test TC cBPF unbound bytecode doesn't offload...")
865 ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
866 fail=False, include_stderr=True)
867 fail(ret == 0, "TC bytecode loaded for offload")
868 check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
872 start_test("Test non-0 chain offload...")
873 ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1,
875 fail=False, include_stderr=True)
876 fail(ret == 0, "Offloaded a filter to chain other than 0")
877 check_extack(err, "Driver supports only offload of chain 0.", args)
878 sim.tc_flush_filters()
880 start_test("Test TC replace...")
881 sim.cls_bpf_add_filter(obj, prio=1, handle=1)
882 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1)
883 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
885 sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True)
886 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True)
887 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
889 sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True)
890 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True)
891 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
893 start_test("Test TC replace bad flags...")
896 ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
897 skip_sw=(j == 1), skip_hw=(j == 2),
899 fail(bool(ret) != bool(j),
900 "Software TC incorrect load in replace test, iteration %d" %
902 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
904 start_test("Test spurious extack from the driver...")
905 test_spurios_extack(sim, obj, False, "netdevsim")
906 test_spurios_extack(sim, obj, True, "netdevsim")
908 sim.set_ethtool_tc_offloads(False)
910 test_spurios_extack(sim, obj, False, "TC offload is disabled")
911 test_spurios_extack(sim, obj, True, "TC offload is disabled")
913 sim.set_ethtool_tc_offloads(True)
915 sim.tc_flush_filters()
917 start_test("Test TC offloads failure...")
918 sim.dfs["dev/bpf_bind_verifier_accept"] = 0
919 ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
920 fail=False, include_stderr=True)
921 fail(ret == 0, "TC filter did not reject with TC offloads enabled")
922 check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
923 sim.dfs["dev/bpf_bind_verifier_accept"] = 1
925 start_test("Test TC offloads work...")
926 ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
927 fail=False, include_stderr=True)
928 fail(ret != 0, "TC filter did not load with TC offloads enabled")
930 start_test("Test TC offload basics...")
931 dfs = simdev.dfs_get_bound_progs(expected=1)
932 progs = bpftool_prog_list(expected=1)
933 ingress = sim.tc_show_ingress(expected=1)
938 fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
939 fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
940 fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
942 start_test("Test TC offload is device-bound...")
943 fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
944 fail(prog["tag"] != fltr["tag"], "Program tags don't match")
945 fail(fltr["id"] != dprog["id"], "Program IDs don't match")
946 fail(dprog["state"] != "xlated", "Offloaded program state not translated")
947 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
949 start_test("Test disabling TC offloads is rejected while filters installed...")
950 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
951 fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
952 sim.set_ethtool_tc_offloads(True)
954 start_test("Test qdisc removal frees things...")
955 sim.tc_flush_filters()
956 sim.tc_show_ingress(expected=0)
958 start_test("Test disabling TC offloads is OK without filters...")
959 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
961 "Driver refused to disable TC offloads without filters installed...")
963 sim.set_ethtool_tc_offloads(True)
965 start_test("Test destroying device gets rid of TC filters...")
966 sim.cls_bpf_add_filter(obj, skip_sw=True)
968 bpftool_prog_list_wait(expected=0)
970 simdev = NetdevSimDev()
972 sim.set_ethtool_tc_offloads(True)
974 start_test("Test destroying device gets rid of XDP...")
975 sim.set_xdp(obj, "offload")
977 bpftool_prog_list_wait(expected=0)
979 simdev = NetdevSimDev()
981 sim.set_ethtool_tc_offloads(True)
983 start_test("Test XDP prog reporting...")
984 sim.set_xdp(obj, "drv")
985 ipl = sim.ip_link_show(xdp=True)
986 progs = bpftool_prog_list(expected=1)
987 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
988 "Loaded program has wrong ID")
990 start_test("Test XDP prog replace without force...")
991 ret, _ = sim.set_xdp(obj, "drv", fail=False)
992 fail(ret == 0, "Replaced XDP program without -force")
993 sim.wait_for_flush(total=1)
995 start_test("Test XDP prog replace with force...")
996 ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
997 fail(ret != 0, "Could not replace XDP program with -force")
998 bpftool_prog_list_wait(expected=1)
999 ipl = sim.ip_link_show(xdp=True)
1000 progs = bpftool_prog_list(expected=1)
1001 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
1002 "Loaded program has wrong ID")
1003 fail("dev" in progs[0].keys(),
1004 "Device parameters reported for non-offloaded program")
1006 start_test("Test XDP prog replace with bad flags...")
1007 ret, _, err = sim.set_xdp(obj, "generic", force=True,
1008 fail=False, include_stderr=True)
1009 fail(ret == 0, "Replaced XDP program with a program in different mode")
1011 "Native and generic XDP can't be active at the same time.",
1014 start_test("Test MTU restrictions...")
1015 ret, _ = sim.set_mtu(9000, fail=False)
1017 "Driver should refuse to increase MTU to 9000 with XDP loaded...")
1018 sim.unset_xdp("drv")
1019 bpftool_prog_list_wait(expected=0)
1021 ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True)
1022 fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
1023 check_extack_nsim(err, "MTU too large w/ XDP enabled.", args)
1026 sim.wait_for_flush()
1027 start_test("Test non-offload XDP attaching to HW...")
1028 bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/nooffload")
1029 nooffload = bpf_pinned("/sys/fs/bpf/nooffload")
1030 ret, _, err = sim.set_xdp(nooffload, "offload",
1031 fail=False, include_stderr=True)
1032 fail(ret == 0, "attached non-offloaded XDP program to HW")
1033 check_extack_nsim(err, "xdpoffload of non-bound program.", args)
1034 rm("/sys/fs/bpf/nooffload")
1036 start_test("Test offload XDP attaching to drv...")
1037 bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/offload",
1039 offload = bpf_pinned("/sys/fs/bpf/offload")
1040 ret, _, err = sim.set_xdp(offload, "drv", fail=False, include_stderr=True)
1041 fail(ret == 0, "attached offloaded XDP program to drv")
1042 check_extack(err, "Using offloaded program without HW_MODE flag is not supported.", args)
1043 rm("/sys/fs/bpf/offload")
1044 sim.wait_for_flush()
1046 start_test("Test XDP load failure...")
1047 sim.dfs["dev/bpf_bind_verifier_accept"] = 0
1048 ret, _, err = bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/offload",
1049 dev=sim['ifname'], fail=False, include_stderr=True)
1050 fail(ret == 0, "verifier should fail on load")
1051 check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
1052 sim.dfs["dev/bpf_bind_verifier_accept"] = 1
1053 sim.wait_for_flush()
1055 start_test("Test XDP offload...")
1056 _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
1057 ipl = sim.ip_link_show(xdp=True)
1058 link_xdp = ipl["xdp"]["prog"]
1059 progs = bpftool_prog_list(expected=1)
1061 fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
1063 start_test("Test XDP offload is device bound...")
1064 dfs = simdev.dfs_get_bound_progs(expected=1)
1067 fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
1068 fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
1069 fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
1070 fail(dprog["state"] != "xlated", "Offloaded program state not translated")
1071 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
1073 start_test("Test removing XDP program many times...")
1074 sim.unset_xdp("offload")
1075 sim.unset_xdp("offload")
1076 sim.unset_xdp("drv")
1077 sim.unset_xdp("drv")
1080 bpftool_prog_list_wait(expected=0)
1082 start_test("Test attempt to use a program for a wrong device...")
1083 simdev2 = NetdevSimDev()
1084 sim2, = simdev2.nsims
1085 sim2.set_xdp(obj, "offload")
1086 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
1088 ret, _, err = sim.set_xdp(pinned, "offload",
1089 fail=False, include_stderr=True)
1090 fail(ret == 0, "Pinned program loaded for a different device accepted")
1091 check_extack(err, "Program bound to different device.", args)
1093 ret, _, err = sim.set_xdp(pinned, "offload",
1094 fail=False, include_stderr=True)
1095 fail(ret == 0, "Pinned program loaded for a removed device accepted")
1096 check_extack(err, "Program bound to different device.", args)
1098 bpftool_prog_list_wait(expected=0)
1100 simdev, sim = test_multi_prog(simdev, sim, obj, "", 1)
1101 simdev, sim = test_multi_prog(simdev, sim, obj, "drv", 1)
1102 simdev, sim = test_multi_prog(simdev, sim, obj, "generic", 2)
1104 start_test("Test mixing of TC and XDP...")
1105 sim.tc_add_ingress()
1106 sim.set_xdp(obj, "offload")
1107 ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
1108 fail=False, include_stderr=True)
1109 fail(ret == 0, "Loading TC when XDP active should fail")
1110 check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
1111 sim.unset_xdp("offload")
1112 sim.wait_for_flush()
1114 sim.cls_bpf_add_filter(obj, skip_sw=True)
1115 ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
1116 fail(ret == 0, "Loading XDP when TC active should fail")
1117 check_extack_nsim(err, "TC program is already loaded.", args)
1119 start_test("Test binding TC from pinned...")
1120 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
1121 sim.tc_flush_filters(bound=1, total=1)
1122 sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
1123 sim.tc_flush_filters(bound=1, total=1)
1125 start_test("Test binding XDP from pinned...")
1126 sim.set_xdp(obj, "offload")
1127 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
1129 sim.set_xdp(pinned, "offload", force=True)
1130 sim.unset_xdp("offload")
1131 sim.set_xdp(pinned, "offload", force=True)
1132 sim.unset_xdp("offload")
1134 start_test("Test offload of wrong type fails...")
1135 ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
1136 fail(ret == 0, "Managed to attach XDP program to TC")
1138 start_test("Test asking for TC offload of two filters...")
1139 sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
1140 ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
1141 fail=False, include_stderr=True)
1142 fail(ret == 0, "Managed to offload two TC filters at the same time")
1143 check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
1145 sim.tc_flush_filters(bound=2, total=2)
1147 start_test("Test if netdev removal waits for translation...")
1149 sim.dfs["dev/bpf_bind_verifier_delay"] = delay_msec
1151 cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
1152 (sim['ifname'], obj)
1153 tc_proc = cmd(cmd_line, background=True, fail=False)
1154 # Wait for the verifier to start
1155 while simdev.dfs_num_bound_progs() <= 2:
1159 ret, _ = cmd_result(tc_proc, fail=False)
1160 time_diff = end - start
1161 log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
1163 fail(ret == 0, "Managed to load TC filter on a unregistering device")
1164 delay_sec = delay_msec * 0.001
1165 fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
1166 (time_diff, delay_sec))
1168 # Remove all pinned files and reinstantiate the netdev
1170 bpftool_prog_list_wait(expected=0)
1172 simdev = NetdevSimDev()
1174 map_obj = bpf_obj("sample_map_ret0.bpf.o")
1175 start_test("Test loading program with maps...")
1176 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1178 start_test("Test bpftool bound info reporting (own ns)...")
1179 check_dev_info(False, "")
1181 start_test("Test bpftool bound info reporting (other ns)...")
1184 check_dev_info(True, "")
1186 start_test("Test bpftool bound info reporting (remote ns)...")
1187 check_dev_info(False, ns)
1189 start_test("Test bpftool bound info reporting (back to own ns)...")
1191 check_dev_info(False, "")
1193 prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
1194 map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
1197 start_test("Test bpftool bound info reporting (removed dev)...")
1198 check_dev_info_removed(prog_file=prog_file, map_file=map_file)
1200 # Remove all pinned files and reinstantiate the netdev
1202 bpftool_prog_list_wait(expected=0)
1204 simdev = NetdevSimDev()
1207 start_test("Test map update (no flags)...")
1208 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1209 maps = bpftool_map_list(expected=2)
1210 array = maps[0] if maps[0]["type"] == "array" else maps[1]
1211 htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
1214 bpftool("map update id %d key %s value %s" %
1215 (m["id"], int2str("I", i), int2str("Q", i * 3)))
1218 ret, _ = bpftool("map update id %d key %s value %s" %
1219 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
1221 fail(ret == 0, "added too many entries")
1223 start_test("Test map update (exists)...")
1226 bpftool("map update id %d key %s value %s exist" %
1227 (m["id"], int2str("I", i), int2str("Q", i * 3)))
1230 ret, err = bpftool("map update id %d key %s value %s exist" %
1231 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
1233 fail(ret == 0, "updated non-existing key")
1234 fail(err["error"].find("No such file or directory") == -1,
1235 "expected ENOENT, error is '%s'" % (err["error"]))
1237 start_test("Test map update (noexist)...")
1240 ret, err = bpftool("map update id %d key %s value %s noexist" %
1241 (m["id"], int2str("I", i), int2str("Q", i * 3)),
1243 fail(ret == 0, "updated existing key")
1244 fail(err["error"].find("File exists") == -1,
1245 "expected EEXIST, error is '%s'" % (err["error"]))
1247 start_test("Test map dump...")
1249 _, entries = bpftool("map dump id %d" % (m["id"]))
1251 key = str2int(entries[i]["key"])
1252 fail(key != i, "expected key %d, got %d" % (key, i))
1253 val = str2int(entries[i]["value"])
1254 fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
1256 start_test("Test map getnext...")
1258 _, entry = bpftool("map getnext id %d" % (m["id"]))
1259 key = str2int(entry["next_key"])
1260 fail(key != 0, "next key %d, expected %d" % (key, 0))
1261 _, entry = bpftool("map getnext id %d key %s" %
1262 (m["id"], int2str("I", 0)))
1263 key = str2int(entry["next_key"])
1264 fail(key != 1, "next key %d, expected %d" % (key, 1))
1265 ret, err = bpftool("map getnext id %d key %s" %
1266 (m["id"], int2str("I", 1)), fail=False)
1267 fail(ret == 0, "got next key past the end of map")
1268 fail(err["error"].find("No such file or directory") == -1,
1269 "expected ENOENT, error is '%s'" % (err["error"]))
1271 start_test("Test map delete (htab)...")
1273 bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
1275 start_test("Test map delete (array)...")
1277 ret, err = bpftool("map delete id %d key %s" %
1278 (htab["id"], int2str("I", i)), fail=False)
1279 fail(ret == 0, "removed entry from an array")
1280 fail(err["error"].find("No such file or directory") == -1,
1281 "expected ENOENT, error is '%s'" % (err["error"]))
1283 start_test("Test map remove...")
1284 sim.unset_xdp("offload")
1285 bpftool_map_list_wait(expected=0)
1288 simdev = NetdevSimDev()
1290 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1292 bpftool_map_list_wait(expected=0)
1294 start_test("Test map creation fail path...")
1295 simdev = NetdevSimDev()
1297 sim.dfs["bpf_map_accept"] = "N"
1298 ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
1300 "netdevsim didn't refuse to create a map with offload disabled")
1304 start_test("Test multi-dev ASIC program reuse...")
1305 simdevA = NetdevSimDev()
1306 simA, = simdevA.nsims
1307 simdevB = NetdevSimDev(3)
1308 simB1, simB2, simB3 = simdevB.nsims
1309 sims = (simA, simB1, simB2, simB3)
1310 simB = (simB1, simB2, simB3)
1312 bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimA",
1314 progA = bpf_pinned("/sys/fs/bpf/nsimA")
1315 bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB",
1316 dev=simB1['ifname'])
1317 progB = bpf_pinned("/sys/fs/bpf/nsimB")
1319 simA.set_xdp(progA, "offload", JSON=False)
1320 for d in simdevB.nsims:
1321 d.set_xdp(progB, "offload", JSON=False)
1323 start_test("Test multi-dev ASIC cross-dev replace...")
1324 ret, _ = simA.set_xdp(progB, "offload", force=True, JSON=False, fail=False)
1325 fail(ret == 0, "cross-ASIC program allowed")
1326 for d in simdevB.nsims:
1327 ret, _ = d.set_xdp(progA, "offload", force=True, JSON=False, fail=False)
1328 fail(ret == 0, "cross-ASIC program allowed")
1330 start_test("Test multi-dev ASIC cross-dev install...")
1332 d.unset_xdp("offload")
1334 ret, _, err = simA.set_xdp(progB, "offload", force=True, JSON=False,
1335 fail=False, include_stderr=True)
1336 fail(ret == 0, "cross-ASIC program allowed")
1337 check_extack(err, "Program bound to different device.", args)
1338 for d in simdevB.nsims:
1339 ret, _, err = d.set_xdp(progA, "offload", force=True, JSON=False,
1340 fail=False, include_stderr=True)
1341 fail(ret == 0, "cross-ASIC program allowed")
1342 check_extack(err, "Program bound to different device.", args)
1344 start_test("Test multi-dev ASIC cross-dev map reuse...")
1346 mapA = bpftool("prog show %s" % (progA))[1]["map_ids"][0]
1347 mapB = bpftool("prog show %s" % (progB))[1]["map_ids"][0]
1349 ret, _ = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB_",
1350 dev=simB3['ifname'],
1351 maps=["idx 0 id %d" % (mapB)],
1353 fail(ret != 0, "couldn't reuse a map on the same ASIC")
1354 rm("/sys/fs/bpf/nsimB_")
1356 ret, _, err = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimA_",
1358 maps=["idx 0 id %d" % (mapB)],
1359 fail=False, include_stderr=True)
1360 fail(ret == 0, "could reuse a map on a different ASIC")
1361 fail(err.count("offload device mismatch between prog and map") == 0,
1362 "error message missing for cross-ASIC map")
1364 ret, _, err = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB_",
1365 dev=simB1['ifname'],
1366 maps=["idx 0 id %d" % (mapA)],
1367 fail=False, include_stderr=True)
1368 fail(ret == 0, "could reuse a map on a different ASIC")
1369 fail(err.count("offload device mismatch between prog and map") == 0,
1370 "error message missing for cross-ASIC map")
1372 start_test("Test multi-dev ASIC cross-dev destruction...")
1373 bpftool_prog_list_wait(expected=2)
1376 bpftool_prog_list_wait(expected=1)
1378 ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
1379 fail(ifnameB != simB1['ifname'], "program not bound to original device")
1381 bpftool_prog_list_wait(expected=1)
1383 start_test("Test multi-dev ASIC cross-dev destruction - move...")
1384 ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
1385 fail(ifnameB not in (simB2['ifname'], simB3['ifname']),
1386 "program not bound to remaining devices")
1389 ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
1390 fail(ifnameB != simB3['ifname'], "program not bound to remaining device")
1394 bpftool_prog_list_wait(expected=0)
1396 start_test("Test multi-dev ASIC cross-dev destruction - orphaned...")
1397 ret, out = bpftool("prog show %s" % (progB), fail=False)
1398 fail(ret == 0, "got information about orphaned program")
1399 fail("error" not in out, "no error reported for get info on orphaned")
1400 fail(out["error"] != "can't get prog info: No such device",
1401 "wrong error for get info on orphaned")
1403 print("%s: OK" % (os.path.basename(__file__)))
1406 log("Clean up...", "", level=1)