selftests: move bpf-offload test from bpf to net
authorJakub Kicinski <kuba@kernel.org>
Tue, 9 Apr 2024 03:15:46 +0000 (20:15 -0700)
committerJakub Kicinski <kuba@kernel.org>
Wed, 10 Apr 2024 21:03:12 +0000 (14:03 -0700)
We're building more python tests on the netdev side, and some
of the classes from the venerable BPF offload tests can be reused.

Acked-by: Stanislav Fomichev <sdf@google.com>
Acked-by: Martin KaFai Lau <martin.lau@kernel.org>
Link: https://lore.kernel.org/r/20240409031549.3531084-2-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
tools/testing/selftests/bpf/Makefile
tools/testing/selftests/bpf/progs/sample_map_ret0.c [deleted file]
tools/testing/selftests/bpf/progs/sample_ret0.c [deleted file]
tools/testing/selftests/bpf/test_offload.py [deleted file]
tools/testing/selftests/net/Makefile
tools/testing/selftests/net/bpf_offload.py [new file with mode: 0755]
tools/testing/selftests/net/sample_map_ret0.bpf.c [new file with mode: 0644]
tools/testing/selftests/net/sample_ret0.bpf.c [new file with mode: 0644]

index 3b9eb40d63436f84b1fe63e9a85f0b0f6154863d..b0be07f29ddebef9563adcc5f62b8395dffdaadf 100644 (file)
@@ -102,7 +102,6 @@ TEST_PROGS := test_kmod.sh \
        test_xdp_redirect_multi.sh \
        test_xdp_meta.sh \
        test_xdp_veth.sh \
-       test_offload.py \
        test_sock_addr.sh \
        test_tunnel.sh \
        test_lwt_seg6local.sh \
diff --git a/tools/testing/selftests/bpf/progs/sample_map_ret0.c b/tools/testing/selftests/bpf/progs/sample_map_ret0.c
deleted file mode 100644 (file)
index 495990d..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
-#include <linux/bpf.h>
-#include <bpf/bpf_helpers.h>
-
-struct {
-       __uint(type, BPF_MAP_TYPE_HASH);
-       __type(key, __u32);
-       __type(value, long);
-       __uint(max_entries, 2);
-} htab SEC(".maps");
-
-struct {
-       __uint(type, BPF_MAP_TYPE_ARRAY);
-       __type(key, __u32);
-       __type(value, long);
-       __uint(max_entries, 2);
-} array SEC(".maps");
-
-/* Sample program which should always load for testing control paths. */
-SEC(".text") int func()
-{
-       __u64 key64 = 0;
-       __u32 key = 0;
-       long *value;
-
-       value = bpf_map_lookup_elem(&htab, &key);
-       if (!value)
-               return 1;
-       value = bpf_map_lookup_elem(&array, &key64);
-       if (!value)
-               return 1;
-
-       return 0;
-}
diff --git a/tools/testing/selftests/bpf/progs/sample_ret0.c b/tools/testing/selftests/bpf/progs/sample_ret0.c
deleted file mode 100644 (file)
index fec9975..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
-
-/* Sample program which should always load for testing control paths. */
-int func()
-{
-       return 0;
-}
diff --git a/tools/testing/selftests/bpf/test_offload.py b/tools/testing/selftests/bpf/test_offload.py
deleted file mode 100755 (executable)
index 6157f88..0000000
+++ /dev/null
@@ -1,1405 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2017 Netronome Systems, Inc.
-# Copyright (c) 2019 Mellanox Technologies. All rights reserved
-#
-# This software is licensed under the GNU General License Version 2,
-# June 1991 as shown in the file COPYING in the top-level directory of this
-# source tree.
-#
-# THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
-# WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
-# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
-# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
-# THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-from datetime import datetime
-import argparse
-import errno
-import json
-import os
-import pprint
-import random
-import re
-import stat
-import string
-import struct
-import subprocess
-import time
-import traceback
-
-logfile = None
-log_level = 1
-skip_extack = False
-bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
-pp = pprint.PrettyPrinter()
-devs = [] # devices we created for clean up
-files = [] # files to be removed
-netns = [] # net namespaces to be removed
-
-def log_get_sec(level=0):
-    return "*" * (log_level + level)
-
-def log_level_inc(add=1):
-    global log_level
-    log_level += add
-
-def log_level_dec(sub=1):
-    global log_level
-    log_level -= sub
-
-def log_level_set(level):
-    global log_level
-    log_level = level
-
-def log(header, data, level=None):
-    """
-    Output to an optional log.
-    """
-    if logfile is None:
-        return
-    if level is not None:
-        log_level_set(level)
-
-    if not isinstance(data, str):
-        data = pp.pformat(data)
-
-    if len(header):
-        logfile.write("\n" + log_get_sec() + " ")
-        logfile.write(header)
-    if len(header) and len(data.strip()):
-        logfile.write("\n")
-    logfile.write(data)
-
-def skip(cond, msg):
-    if not cond:
-        return
-    print("SKIP: " + msg)
-    log("SKIP: " + msg, "", level=1)
-    os.sys.exit(0)
-
-def fail(cond, msg):
-    if not cond:
-        return
-    print("FAIL: " + msg)
-    tb = "".join(traceback.extract_stack().format())
-    print(tb)
-    log("FAIL: " + msg, tb, level=1)
-    os.sys.exit(1)
-
-def start_test(msg):
-    log(msg, "", level=1)
-    log_level_inc()
-    print(msg)
-
-def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
-    """
-    Run a command in subprocess and return tuple of (retval, stdout);
-    optionally return stderr as well as third value.
-    """
-    proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
-                            stderr=subprocess.PIPE)
-    if background:
-        msg = "%s START: %s" % (log_get_sec(1),
-                                datetime.now().strftime("%H:%M:%S.%f"))
-        log("BKG " + proc.args, msg)
-        return proc
-
-    return cmd_result(proc, include_stderr=include_stderr, fail=fail)
-
-def cmd_result(proc, include_stderr=False, fail=False):
-    stdout, stderr = proc.communicate()
-    stdout = stdout.decode("utf-8")
-    stderr = stderr.decode("utf-8")
-    proc.stdout.close()
-    proc.stderr.close()
-
-    stderr = "\n" + stderr
-    if stderr[-1] == "\n":
-        stderr = stderr[:-1]
-
-    sec = log_get_sec(1)
-    log("CMD " + proc.args,
-        "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
-        (proc.returncode, sec, stdout, sec, stderr,
-         sec, datetime.now().strftime("%H:%M:%S.%f")))
-
-    if proc.returncode != 0 and fail:
-        if len(stderr) > 0 and stderr[-1] == "\n":
-            stderr = stderr[:-1]
-        raise Exception("Command failed: %s\n%s" % (proc.args, stderr))
-
-    if include_stderr:
-        return proc.returncode, stdout, stderr
-    else:
-        return proc.returncode, stdout
-
-def rm(f):
-    cmd("rm -f %s" % (f))
-    if f in files:
-        files.remove(f)
-
-def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False):
-    params = ""
-    if JSON:
-        params += "%s " % (flags["json"])
-
-    if ns != "":
-        ns = "ip netns exec %s " % (ns)
-
-    if include_stderr:
-        ret, stdout, stderr = cmd(ns + name + " " + params + args,
-                                  fail=fail, include_stderr=True)
-    else:
-        ret, stdout = cmd(ns + name + " " + params + args,
-                          fail=fail, include_stderr=False)
-
-    if JSON and len(stdout.strip()) != 0:
-        out = json.loads(stdout)
-    else:
-        out = stdout
-
-    if include_stderr:
-        return ret, out, stderr
-    else:
-        return ret, out
-
-def bpftool(args, JSON=True, ns="", fail=True, include_stderr=False):
-    return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns,
-                fail=fail, include_stderr=include_stderr)
-
-def bpftool_prog_list(expected=None, ns="", exclude_orphaned=True):
-    _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
-    # Remove the base progs
-    for p in base_progs:
-        if p in progs:
-            progs.remove(p)
-    if exclude_orphaned:
-        progs = [ p for p in progs if not p['orphaned'] ]
-    if expected is not None:
-        if len(progs) != expected:
-            fail(True, "%d BPF programs loaded, expected %d" %
-                 (len(progs), expected))
-    return progs
-
-def bpftool_map_list(expected=None, ns=""):
-    _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
-    # Remove the base maps
-    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]
-    if expected is not None:
-        if len(maps) != expected:
-            fail(True, "%d BPF maps loaded, expected %d" %
-                 (len(maps), expected))
-    return maps
-
-def bpftool_prog_list_wait(expected=0, n_retry=20):
-    for i in range(n_retry):
-        nprogs = len(bpftool_prog_list())
-        if nprogs == expected:
-            return
-        time.sleep(0.05)
-    raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
-
-def bpftool_map_list_wait(expected=0, n_retry=20):
-    for i in range(n_retry):
-        nmaps = len(bpftool_map_list())
-        if nmaps == expected:
-            return
-        time.sleep(0.05)
-    raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))
-
-def bpftool_prog_load(sample, file_name, maps=[], prog_type="xdp", dev=None,
-                      fail=True, include_stderr=False):
-    args = "prog load %s %s" % (os.path.join(bpf_test_dir, sample), file_name)
-    if prog_type is not None:
-        args += " type " + prog_type
-    if dev is not None:
-        args += " dev " + dev
-    if len(maps):
-        args += " map " + " map ".join(maps)
-
-    res = bpftool(args, fail=fail, include_stderr=include_stderr)
-    if res[0] == 0:
-        files.append(file_name)
-    return res
-
-def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False):
-    if force:
-        args = "-force " + args
-    return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns,
-                fail=fail, include_stderr=include_stderr)
-
-def tc(args, JSON=True, ns="", fail=True, include_stderr=False):
-    return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns,
-                fail=fail, include_stderr=include_stderr)
-
-def ethtool(dev, opt, args, fail=True):
-    return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)
-
-def bpf_obj(name, sec=".text", path=bpf_test_dir,):
-    return "obj %s sec %s" % (os.path.join(path, name), sec)
-
-def bpf_pinned(name):
-    return "pinned %s" % (name)
-
-def bpf_bytecode(bytecode):
-    return "bytecode \"%s\"" % (bytecode)
-
-def mknetns(n_retry=10):
-    for i in range(n_retry):
-        name = ''.join([random.choice(string.ascii_letters) for i in range(8)])
-        ret, _ = ip("netns add %s" % (name), fail=False)
-        if ret == 0:
-            netns.append(name)
-            return name
-    return None
-
-def int2str(fmt, val):
-    ret = []
-    for b in struct.pack(fmt, val):
-        ret.append(int(b))
-    return " ".join(map(lambda x: str(x), ret))
-
-def str2int(strtab):
-    inttab = []
-    for i in strtab:
-        inttab.append(int(i, 16))
-    ba = bytearray(inttab)
-    if len(strtab) == 4:
-        fmt = "I"
-    elif len(strtab) == 8:
-        fmt = "Q"
-    else:
-        raise Exception("String array of len %d can't be unpacked to an int" %
-                        (len(strtab)))
-    return struct.unpack(fmt, ba)[0]
-
-class DebugfsDir:
-    """
-    Class for accessing DebugFS directories as a dictionary.
-    """
-
-    def __init__(self, path):
-        self.path = path
-        self._dict = self._debugfs_dir_read(path)
-
-    def __len__(self):
-        return len(self._dict.keys())
-
-    def __getitem__(self, key):
-        if type(key) is int:
-            key = list(self._dict.keys())[key]
-        return self._dict[key]
-
-    def __setitem__(self, key, value):
-        log("DebugFS set %s = %s" % (key, value), "")
-        log_level_inc()
-
-        cmd("echo '%s' > %s/%s" % (value, self.path, key))
-        log_level_dec()
-
-        _, out = cmd('cat %s/%s' % (self.path, key))
-        self._dict[key] = out.strip()
-
-    def _debugfs_dir_read(self, path):
-        dfs = {}
-
-        log("DebugFS state for %s" % (path), "")
-        log_level_inc(add=2)
-
-        _, out = cmd('ls ' + path)
-        for f in out.split():
-            if f == "ports":
-                continue
-
-            p = os.path.join(path, f)
-            if not os.stat(p).st_mode & stat.S_IRUSR:
-                continue
-
-            if os.path.isfile(p):
-                # We need to init trap_flow_action_cookie before read it
-                if f == "trap_flow_action_cookie":
-                    cmd('echo deadbeef > %s/%s' % (path, f))
-                _, out = cmd('cat %s/%s' % (path, f))
-                dfs[f] = out.strip()
-            elif os.path.isdir(p):
-                dfs[f] = DebugfsDir(p)
-            else:
-                raise Exception("%s is neither file nor directory" % (p))
-
-        log_level_dec()
-        log("DebugFS state", dfs)
-        log_level_dec()
-
-        return dfs
-
-class NetdevSimDev:
-    """
-    Class for netdevsim bus device and its attributes.
-    """
-    @staticmethod
-    def ctrl_write(path, val):
-        fullpath = os.path.join("/sys/bus/netdevsim/", path)
-        try:
-            with open(fullpath, "w") as f:
-                f.write(val)
-        except OSError as e:
-            log("WRITE %s: %r" % (fullpath, val), -e.errno)
-            raise e
-        log("WRITE %s: %r" % (fullpath, val), 0)
-
-    def __init__(self, port_count=1):
-        addr = 0
-        while True:
-            try:
-                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
-            except OSError as e:
-                if e.errno == errno.ENOSPC:
-                    addr += 1
-                    continue
-                raise e
-            break
-        self.addr = addr
-
-        # As probe of netdevsim device might happen from a workqueue,
-        # so wait here until all netdevs appear.
-        self.wait_for_netdevs(port_count)
-
-        ret, out = cmd("udevadm settle", fail=False)
-        if ret:
-            raise Exception("udevadm settle failed")
-        ifnames = self.get_ifnames()
-
-        devs.append(self)
-        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
-
-        self.nsims = []
-        for port_index in range(port_count):
-            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index]))
-
-    def get_ifnames(self):
-        ifnames = []
-        listdir = os.listdir("/sys/bus/netdevsim/devices/netdevsim%u/net/" % self.addr)
-        for ifname in listdir:
-            ifnames.append(ifname)
-        ifnames.sort()
-        return ifnames
-
-    def wait_for_netdevs(self, port_count):
-        timeout = 5
-        timeout_start = time.time()
-
-        while True:
-            try:
-                ifnames = self.get_ifnames()
-            except FileNotFoundError as e:
-                ifnames = []
-            if len(ifnames) == port_count:
-                break
-            if time.time() < timeout_start + timeout:
-                continue
-            raise Exception("netdevices did not appear within timeout")
-
-    def dfs_num_bound_progs(self):
-        path = os.path.join(self.dfs_dir, "bpf_bound_progs")
-        _, progs = cmd('ls %s' % (path))
-        return len(progs.split())
-
-    def dfs_get_bound_progs(self, expected):
-        progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
-        if expected is not None:
-            if len(progs) != expected:
-                fail(True, "%d BPF programs bound, expected %d" %
-                     (len(progs), expected))
-        return progs
-
-    def remove(self):
-        self.ctrl_write("del_device", "%u" % (self.addr, ))
-        devs.remove(self)
-
-    def remove_nsim(self, nsim):
-        self.nsims.remove(nsim)
-        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
-                        "%u" % (nsim.port_index, ))
-
-class NetdevSim:
-    """
-    Class for netdevsim netdevice and its attributes.
-    """
-
-    def __init__(self, nsimdev, port_index, ifname):
-        # In case udev renamed the netdev to according to new schema,
-        # check if the name matches the port_index.
-        nsimnamere = re.compile("eni\d+np(\d+)")
-        match = nsimnamere.match(ifname)
-        if match and int(match.groups()[0]) != port_index + 1:
-            raise Exception("netdevice name mismatches the expected one")
-
-        self.nsimdev = nsimdev
-        self.port_index = port_index
-        self.ns = ""
-        self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
-        self.dfs_refresh()
-        _, [self.dev] = ip("link show dev %s" % ifname)
-
-    def __getitem__(self, key):
-        return self.dev[key]
-
-    def remove(self):
-        self.nsimdev.remove_nsim(self)
-
-    def dfs_refresh(self):
-        self.dfs = DebugfsDir(self.dfs_dir)
-        return self.dfs
-
-    def dfs_read(self, f):
-        path = os.path.join(self.dfs_dir, f)
-        _, data = cmd('cat %s' % (path))
-        return data.strip()
-
-    def wait_for_flush(self, bound=0, total=0, n_retry=20):
-        for i in range(n_retry):
-            nbound = self.nsimdev.dfs_num_bound_progs()
-            nprogs = len(bpftool_prog_list())
-            if nbound == bound and nprogs == total:
-                return
-            time.sleep(0.05)
-        raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
-
-    def set_ns(self, ns):
-        name = "1" if ns == "" else ns
-        ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
-        self.ns = ns
-
-    def set_mtu(self, mtu, fail=True):
-        return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
-                  fail=fail)
-
-    def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
-                fail=True, include_stderr=False):
-        if verbose:
-            bpf += " verbose"
-        return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
-                  force=force, JSON=JSON,
-                  fail=fail, include_stderr=include_stderr)
-
-    def unset_xdp(self, mode, force=False, JSON=True,
-                  fail=True, include_stderr=False):
-        return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
-                  force=force, JSON=JSON,
-                  fail=fail, include_stderr=include_stderr)
-
-    def ip_link_show(self, xdp):
-        _, link = ip("link show dev %s" % (self['ifname']))
-        if len(link) > 1:
-            raise Exception("Multiple objects on ip link show")
-        if len(link) < 1:
-            return {}
-        fail(xdp != "xdp" in link,
-             "XDP program not reporting in iplink (reported %s, expected %s)" %
-             ("xdp" in link, xdp))
-        return link[0]
-
-    def tc_add_ingress(self):
-        tc("qdisc add dev %s ingress" % (self['ifname']))
-
-    def tc_del_ingress(self):
-        tc("qdisc del dev %s ingress" % (self['ifname']))
-
-    def tc_flush_filters(self, bound=0, total=0):
-        self.tc_del_ingress()
-        self.tc_add_ingress()
-        self.wait_for_flush(bound=bound, total=total)
-
-    def tc_show_ingress(self, expected=None):
-        # No JSON support, oh well...
-        flags = ["skip_sw", "skip_hw", "in_hw"]
-        named = ["protocol", "pref", "chain", "handle", "id", "tag"]
-
-        args = "-s filter show dev %s ingress" % (self['ifname'])
-        _, out = tc(args, JSON=False)
-
-        filters = []
-        lines = out.split('\n')
-        for line in lines:
-            words = line.split()
-            if "handle" not in words:
-                continue
-            fltr = {}
-            for flag in flags:
-                fltr[flag] = flag in words
-            for name in named:
-                try:
-                    idx = words.index(name)
-                    fltr[name] = words[idx + 1]
-                except ValueError:
-                    pass
-            filters.append(fltr)
-
-        if expected is not None:
-            fail(len(filters) != expected,
-                 "%d ingress filters loaded, expected %d" %
-                 (len(filters), expected))
-        return filters
-
-    def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None,
-                      chain=None, cls="", params="",
-                      fail=True, include_stderr=False):
-        spec = ""
-        if prio is not None:
-            spec += " prio %d" % (prio)
-        if handle:
-            spec += " handle %s" % (handle)
-        if chain is not None:
-            spec += " chain %d" % (chain)
-
-        return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\
-                  .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec,
-                          cls=cls, params=params),
-                  fail=fail, include_stderr=include_stderr)
-
-    def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None,
-                           chain=None, da=False, verbose=False,
-                           skip_sw=False, skip_hw=False,
-                           fail=True, include_stderr=False):
-        cls = "bpf " + bpf
-
-        params = ""
-        if da:
-            params += " da"
-        if verbose:
-            params += " verbose"
-        if skip_sw:
-            params += " skip_sw"
-        if skip_hw:
-            params += " skip_hw"
-
-        return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls,
-                                  chain=chain, params=params,
-                                  fail=fail, include_stderr=include_stderr)
-
-    def set_ethtool_tc_offloads(self, enable, fail=True):
-        args = "hw-tc-offload %s" % ("on" if enable else "off")
-        return ethtool(self, "-K", args, fail=fail)
-
-################################################################################
-def clean_up():
-    global files, netns, devs
-
-    for dev in devs:
-        dev.remove()
-    for f in files:
-        cmd("rm -f %s" % (f))
-    for ns in netns:
-        cmd("ip netns delete %s" % (ns))
-    files = []
-    netns = []
-
-def pin_prog(file_name, idx=0):
-    progs = bpftool_prog_list(expected=(idx + 1))
-    prog = progs[idx]
-    bpftool("prog pin id %d %s" % (prog["id"], file_name))
-    files.append(file_name)
-
-    return file_name, bpf_pinned(file_name)
-
-def pin_map(file_name, idx=0, expected=1):
-    maps = bpftool_map_list(expected=expected)
-    m = maps[idx]
-    bpftool("map pin id %d %s" % (m["id"], file_name))
-    files.append(file_name)
-
-    return file_name, bpf_pinned(file_name)
-
-def check_dev_info_removed(prog_file=None, map_file=None):
-    bpftool_prog_list(expected=0)
-    bpftool_prog_list(expected=1, exclude_orphaned=False)
-    ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
-    fail(ret != 0, "failed to show prog with removed device")
-
-    bpftool_map_list(expected=0)
-    ret, err = bpftool("map show pin %s" % (map_file), fail=False)
-    fail(ret == 0, "Showing map with removed device did not fail")
-    fail(err["error"].find("No such device") == -1,
-         "Showing map with removed device expected ENODEV, error is %s" %
-         (err["error"]))
-
-def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
-    progs = bpftool_prog_list(expected=1, ns=ns)
-    prog = progs[0]
-
-    fail("dev" not in prog.keys(), "Device parameters not reported")
-    dev = prog["dev"]
-    fail("ifindex" not in dev.keys(), "Device parameters not reported")
-    fail("ns_dev" not in dev.keys(), "Device parameters not reported")
-    fail("ns_inode" not in dev.keys(), "Device parameters not reported")
-
-    if not other_ns:
-        fail("ifname" not in dev.keys(), "Ifname not reported")
-        fail(dev["ifname"] != sim["ifname"],
-             "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
-    else:
-        fail("ifname" in dev.keys(), "Ifname is reported for other ns")
-
-    maps = bpftool_map_list(expected=2, ns=ns)
-    for m in maps:
-        fail("dev" not in m.keys(), "Device parameters not reported")
-        fail(dev != m["dev"], "Map's device different than program's")
-
-def check_extack(output, reference, args):
-    if skip_extack:
-        return
-    lines = output.split("\n")
-    comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference
-    fail(not comp, "Missing or incorrect netlink extack message")
-
-def check_extack_nsim(output, reference, args):
-    check_extack(output, "netdevsim: " + reference, args)
-
-def check_no_extack(res, needle):
-    fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"),
-         "Found '%s' in command output, leaky extack?" % (needle))
-
-def check_verifier_log(output, reference):
-    lines = output.split("\n")
-    for l in reversed(lines):
-        if l == reference:
-            return
-    fail(True, "Missing or incorrect message from netdevsim in verifier log")
-
-def check_multi_basic(two_xdps):
-    fail(two_xdps["mode"] != 4, "Bad mode reported with multiple programs")
-    fail("prog" in two_xdps, "Base program reported in multi program mode")
-    fail(len(two_xdps["attached"]) != 2,
-         "Wrong attached program count with two programs")
-    fail(two_xdps["attached"][0]["prog"]["id"] ==
-         two_xdps["attached"][1]["prog"]["id"],
-         "Offloaded and other programs have the same id")
-
-def test_spurios_extack(sim, obj, skip_hw, needle):
-    res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw,
-                                 include_stderr=True)
-    check_no_extack(res, needle)
-    res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
-                                 skip_hw=skip_hw, include_stderr=True)
-    check_no_extack(res, needle)
-    res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf",
-                            include_stderr=True)
-    check_no_extack(res, needle)
-
-def test_multi_prog(simdev, sim, obj, modename, modeid):
-    start_test("Test multi-attachment XDP - %s + offload..." %
-               (modename or "default", ))
-    sim.set_xdp(obj, "offload")
-    xdp = sim.ip_link_show(xdp=True)["xdp"]
-    offloaded = sim.dfs_read("bpf_offloaded_id")
-    fail("prog" not in xdp, "Base program not reported in single program mode")
-    fail(len(xdp["attached"]) != 1,
-         "Wrong attached program count with one program")
-
-    sim.set_xdp(obj, modename)
-    two_xdps = sim.ip_link_show(xdp=True)["xdp"]
-
-    fail(xdp["attached"][0] not in two_xdps["attached"],
-         "Offload program not reported after other activated")
-    check_multi_basic(two_xdps)
-
-    offloaded2 = sim.dfs_read("bpf_offloaded_id")
-    fail(offloaded != offloaded2,
-         "Offload ID changed after loading other program")
-
-    start_test("Test multi-attachment XDP - replace...")
-    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
-    fail(ret == 0, "Replaced one of programs without -force")
-    check_extack(err, "XDP program already attached.", args)
-
-    start_test("Test multi-attachment XDP - remove without mode...")
-    ret, _, err = sim.unset_xdp("", force=True,
-                                fail=False, include_stderr=True)
-    fail(ret == 0, "Removed program without a mode flag")
-    check_extack(err, "More than one program loaded, unset mode is ambiguous.", args)
-
-    sim.unset_xdp("offload")
-    xdp = sim.ip_link_show(xdp=True)["xdp"]
-    offloaded = sim.dfs_read("bpf_offloaded_id")
-
-    fail(xdp["mode"] != modeid, "Bad mode reported after multiple programs")
-    fail("prog" not in xdp,
-         "Base program not reported after multi program mode")
-    fail(xdp["attached"][0] not in two_xdps["attached"],
-         "Offload program not reported after other activated")
-    fail(len(xdp["attached"]) != 1,
-         "Wrong attached program count with remaining programs")
-    fail(offloaded != "0", "Offload ID reported with only other program left")
-
-    start_test("Test multi-attachment XDP - reattach...")
-    sim.set_xdp(obj, "offload")
-    two_xdps = sim.ip_link_show(xdp=True)["xdp"]
-
-    fail(xdp["attached"][0] not in two_xdps["attached"],
-         "Other program not reported after offload activated")
-    check_multi_basic(two_xdps)
-
-    start_test("Test multi-attachment XDP - device remove...")
-    simdev.remove()
-
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    sim.set_ethtool_tc_offloads(True)
-    return [simdev, sim]
-
-# Parse command line
-parser = argparse.ArgumentParser()
-parser.add_argument("--log", help="output verbose log to given file")
-args = parser.parse_args()
-if args.log:
-    logfile = open(args.log, 'w+')
-    logfile.write("# -*-Org-*-")
-
-log("Prepare...", "", level=1)
-log_level_inc()
-
-# Check permissions
-skip(os.getuid() != 0, "test must be run as root")
-
-# Check tools
-ret, progs = bpftool("prog", fail=False)
-skip(ret != 0, "bpftool not installed")
-base_progs = progs
-_, base_maps = bpftool("map")
-base_map_names = [
-    'pid_iter.rodata', # created on each bpftool invocation
-    'libbpf_det_bind', # created on each bpftool invocation
-]
-
-# Check netdevsim
-if not os.path.isdir("/sys/bus/netdevsim/"):
-    ret, out = cmd("modprobe netdevsim", fail=False)
-    skip(ret != 0, "netdevsim module could not be loaded")
-
-# Check debugfs
-_, out = cmd("mount")
-if out.find("/sys/kernel/debug type debugfs") == -1:
-    cmd("mount -t debugfs none /sys/kernel/debug")
-
-# Check samples are compiled
-samples = ["sample_ret0.bpf.o", "sample_map_ret0.bpf.o"]
-for s in samples:
-    ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
-    skip(ret != 0, "sample %s/%s not found, please compile it" %
-         (bpf_test_dir, s))
-
-# Check if iproute2 is built with libmnl (needed by extack support)
-_, _, err = cmd("tc qdisc delete dev lo handle 0",
-                fail=False, include_stderr=True)
-if err.find("Error: Failed to find qdisc with specified handle.") == -1:
-    print("Warning: no extack message in iproute2 output, libmnl missing?")
-    log("Warning: no extack message in iproute2 output, libmnl missing?", "")
-    skip_extack = True
-
-# Check if net namespaces seem to work
-ns = mknetns()
-skip(ns is None, "Could not create a net namespace")
-cmd("ip netns delete %s" % (ns))
-netns = []
-
-try:
-    obj = bpf_obj("sample_ret0.bpf.o")
-    bytecode = bpf_bytecode("1,6 0 0 4294967295,")
-
-    start_test("Test destruction of generic XDP...")
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    sim.set_xdp(obj, "generic")
-    simdev.remove()
-    bpftool_prog_list_wait(expected=0)
-
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    sim.tc_add_ingress()
-
-    start_test("Test TC non-offloaded...")
-    ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
-    fail(ret != 0, "Software TC filter did not load")
-
-    start_test("Test TC non-offloaded isn't getting bound...")
-    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
-    fail(ret != 0, "Software TC filter did not load")
-    simdev.dfs_get_bound_progs(expected=0)
-
-    sim.tc_flush_filters()
-
-    start_test("Test TC offloads are off by default...")
-    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
-                                         fail=False, include_stderr=True)
-    fail(ret == 0, "TC filter loaded without enabling TC offloads")
-    check_extack(err, "TC offload is disabled on net device.", args)
-    sim.wait_for_flush()
-
-    sim.set_ethtool_tc_offloads(True)
-    sim.dfs["bpf_tc_non_bound_accept"] = "Y"
-
-    start_test("Test TC offload by default...")
-    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
-    fail(ret != 0, "Software TC filter did not load")
-    simdev.dfs_get_bound_progs(expected=0)
-    ingress = sim.tc_show_ingress(expected=1)
-    fltr = ingress[0]
-    fail(not fltr["in_hw"], "Filter not offloaded by default")
-
-    sim.tc_flush_filters()
-
-    start_test("Test TC cBPF bytcode tries offload by default...")
-    ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
-    fail(ret != 0, "Software TC filter did not load")
-    simdev.dfs_get_bound_progs(expected=0)
-    ingress = sim.tc_show_ingress(expected=1)
-    fltr = ingress[0]
-    fail(not fltr["in_hw"], "Bytecode not offloaded by default")
-
-    sim.tc_flush_filters()
-    sim.dfs["bpf_tc_non_bound_accept"] = "N"
-
-    start_test("Test TC cBPF unbound bytecode doesn't offload...")
-    ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
-                                         fail=False, include_stderr=True)
-    fail(ret == 0, "TC bytecode loaded for offload")
-    check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
-                      args)
-    sim.wait_for_flush()
-
-    start_test("Test non-0 chain offload...")
-    ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1,
-                                         skip_sw=True,
-                                         fail=False, include_stderr=True)
-    fail(ret == 0, "Offloaded a filter to chain other than 0")
-    check_extack(err, "Driver supports only offload of chain 0.", args)
-    sim.tc_flush_filters()
-
-    start_test("Test TC replace...")
-    sim.cls_bpf_add_filter(obj, prio=1, handle=1)
-    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1)
-    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
-
-    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True)
-    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True)
-    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
-
-    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True)
-    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True)
-    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
-
-    start_test("Test TC replace bad flags...")
-    for i in range(3):
-        for j in range(3):
-            ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
-                                            skip_sw=(j == 1), skip_hw=(j == 2),
-                                            fail=False)
-            fail(bool(ret) != bool(j),
-                 "Software TC incorrect load in replace test, iteration %d" %
-                 (j))
-        sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
-
-    start_test("Test spurious extack from the driver...")
-    test_spurios_extack(sim, obj, False, "netdevsim")
-    test_spurios_extack(sim, obj, True, "netdevsim")
-
-    sim.set_ethtool_tc_offloads(False)
-
-    test_spurios_extack(sim, obj, False, "TC offload is disabled")
-    test_spurios_extack(sim, obj, True, "TC offload is disabled")
-
-    sim.set_ethtool_tc_offloads(True)
-
-    sim.tc_flush_filters()
-
-    start_test("Test TC offloads failure...")
-    sim.dfs["dev/bpf_bind_verifier_accept"] = 0
-    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
-                                         fail=False, include_stderr=True)
-    fail(ret == 0, "TC filter did not reject with TC offloads enabled")
-    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
-    sim.dfs["dev/bpf_bind_verifier_accept"] = 1
-
-    start_test("Test TC offloads work...")
-    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
-                                         fail=False, include_stderr=True)
-    fail(ret != 0, "TC filter did not load with TC offloads enabled")
-
-    start_test("Test TC offload basics...")
-    dfs = simdev.dfs_get_bound_progs(expected=1)
-    progs = bpftool_prog_list(expected=1)
-    ingress = sim.tc_show_ingress(expected=1)
-
-    dprog = dfs[0]
-    prog = progs[0]
-    fltr = ingress[0]
-    fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
-    fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
-    fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
-
-    start_test("Test TC offload is device-bound...")
-    fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
-    fail(prog["tag"] != fltr["tag"], "Program tags don't match")
-    fail(fltr["id"] != dprog["id"], "Program IDs don't match")
-    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
-    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
-
-    start_test("Test disabling TC offloads is rejected while filters installed...")
-    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
-    fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
-    sim.set_ethtool_tc_offloads(True)
-
-    start_test("Test qdisc removal frees things...")
-    sim.tc_flush_filters()
-    sim.tc_show_ingress(expected=0)
-
-    start_test("Test disabling TC offloads is OK without filters...")
-    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
-    fail(ret != 0,
-         "Driver refused to disable TC offloads without filters installed...")
-
-    sim.set_ethtool_tc_offloads(True)
-
-    start_test("Test destroying device gets rid of TC filters...")
-    sim.cls_bpf_add_filter(obj, skip_sw=True)
-    simdev.remove()
-    bpftool_prog_list_wait(expected=0)
-
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    sim.set_ethtool_tc_offloads(True)
-
-    start_test("Test destroying device gets rid of XDP...")
-    sim.set_xdp(obj, "offload")
-    simdev.remove()
-    bpftool_prog_list_wait(expected=0)
-
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    sim.set_ethtool_tc_offloads(True)
-
-    start_test("Test XDP prog reporting...")
-    sim.set_xdp(obj, "drv")
-    ipl = sim.ip_link_show(xdp=True)
-    progs = bpftool_prog_list(expected=1)
-    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
-         "Loaded program has wrong ID")
-
-    start_test("Test XDP prog replace without force...")
-    ret, _ = sim.set_xdp(obj, "drv", fail=False)
-    fail(ret == 0, "Replaced XDP program without -force")
-    sim.wait_for_flush(total=1)
-
-    start_test("Test XDP prog replace with force...")
-    ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
-    fail(ret != 0, "Could not replace XDP program with -force")
-    bpftool_prog_list_wait(expected=1)
-    ipl = sim.ip_link_show(xdp=True)
-    progs = bpftool_prog_list(expected=1)
-    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
-         "Loaded program has wrong ID")
-    fail("dev" in progs[0].keys(),
-         "Device parameters reported for non-offloaded program")
-
-    start_test("Test XDP prog replace with bad flags...")
-    ret, _, err = sim.set_xdp(obj, "generic", force=True,
-                              fail=False, include_stderr=True)
-    fail(ret == 0, "Replaced XDP program with a program in different mode")
-    check_extack(err,
-                 "Native and generic XDP can't be active at the same time.",
-                 args)
-
-    start_test("Test MTU restrictions...")
-    ret, _ = sim.set_mtu(9000, fail=False)
-    fail(ret == 0,
-         "Driver should refuse to increase MTU to 9000 with XDP loaded...")
-    sim.unset_xdp("drv")
-    bpftool_prog_list_wait(expected=0)
-    sim.set_mtu(9000)
-    ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True)
-    fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
-    check_extack_nsim(err, "MTU too large w/ XDP enabled.", args)
-    sim.set_mtu(1500)
-
-    sim.wait_for_flush()
-    start_test("Test non-offload XDP attaching to HW...")
-    bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/nooffload")
-    nooffload = bpf_pinned("/sys/fs/bpf/nooffload")
-    ret, _, err = sim.set_xdp(nooffload, "offload",
-                              fail=False, include_stderr=True)
-    fail(ret == 0, "attached non-offloaded XDP program to HW")
-    check_extack_nsim(err, "xdpoffload of non-bound program.", args)
-    rm("/sys/fs/bpf/nooffload")
-
-    start_test("Test offload XDP attaching to drv...")
-    bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/offload",
-                      dev=sim['ifname'])
-    offload = bpf_pinned("/sys/fs/bpf/offload")
-    ret, _, err = sim.set_xdp(offload, "drv", fail=False, include_stderr=True)
-    fail(ret == 0, "attached offloaded XDP program to drv")
-    check_extack(err, "Using offloaded program without HW_MODE flag is not supported.", args)
-    rm("/sys/fs/bpf/offload")
-    sim.wait_for_flush()
-
-    start_test("Test XDP load failure...")
-    sim.dfs["dev/bpf_bind_verifier_accept"] = 0
-    ret, _, err = bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/offload",
-                                 dev=sim['ifname'], fail=False, include_stderr=True)
-    fail(ret == 0, "verifier should fail on load")
-    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
-    sim.dfs["dev/bpf_bind_verifier_accept"] = 1
-    sim.wait_for_flush()
-
-    start_test("Test XDP offload...")
-    _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
-    ipl = sim.ip_link_show(xdp=True)
-    link_xdp = ipl["xdp"]["prog"]
-    progs = bpftool_prog_list(expected=1)
-    prog = progs[0]
-    fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
-
-    start_test("Test XDP offload is device bound...")
-    dfs = simdev.dfs_get_bound_progs(expected=1)
-    dprog = dfs[0]
-
-    fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
-    fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
-    fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
-    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
-    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
-
-    start_test("Test removing XDP program many times...")
-    sim.unset_xdp("offload")
-    sim.unset_xdp("offload")
-    sim.unset_xdp("drv")
-    sim.unset_xdp("drv")
-    sim.unset_xdp("")
-    sim.unset_xdp("")
-    bpftool_prog_list_wait(expected=0)
-
-    start_test("Test attempt to use a program for a wrong device...")
-    simdev2 = NetdevSimDev()
-    sim2, = simdev2.nsims
-    sim2.set_xdp(obj, "offload")
-    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
-
-    ret, _, err = sim.set_xdp(pinned, "offload",
-                              fail=False, include_stderr=True)
-    fail(ret == 0, "Pinned program loaded for a different device accepted")
-    check_extack(err, "Program bound to different device.", args)
-    simdev2.remove()
-    ret, _, err = sim.set_xdp(pinned, "offload",
-                              fail=False, include_stderr=True)
-    fail(ret == 0, "Pinned program loaded for a removed device accepted")
-    check_extack(err, "Program bound to different device.", args)
-    rm(pin_file)
-    bpftool_prog_list_wait(expected=0)
-
-    simdev, sim = test_multi_prog(simdev, sim, obj, "", 1)
-    simdev, sim = test_multi_prog(simdev, sim, obj, "drv", 1)
-    simdev, sim = test_multi_prog(simdev, sim, obj, "generic", 2)
-
-    start_test("Test mixing of TC and XDP...")
-    sim.tc_add_ingress()
-    sim.set_xdp(obj, "offload")
-    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
-                                         fail=False, include_stderr=True)
-    fail(ret == 0, "Loading TC when XDP active should fail")
-    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
-    sim.unset_xdp("offload")
-    sim.wait_for_flush()
-
-    sim.cls_bpf_add_filter(obj, skip_sw=True)
-    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
-    fail(ret == 0, "Loading XDP when TC active should fail")
-    check_extack_nsim(err, "TC program is already loaded.", args)
-
-    start_test("Test binding TC from pinned...")
-    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
-    sim.tc_flush_filters(bound=1, total=1)
-    sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
-    sim.tc_flush_filters(bound=1, total=1)
-
-    start_test("Test binding XDP from pinned...")
-    sim.set_xdp(obj, "offload")
-    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
-
-    sim.set_xdp(pinned, "offload", force=True)
-    sim.unset_xdp("offload")
-    sim.set_xdp(pinned, "offload", force=True)
-    sim.unset_xdp("offload")
-
-    start_test("Test offload of wrong type fails...")
-    ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
-    fail(ret == 0, "Managed to attach XDP program to TC")
-
-    start_test("Test asking for TC offload of two filters...")
-    sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
-    ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
-                                         fail=False, include_stderr=True)
-    fail(ret == 0, "Managed to offload two TC filters at the same time")
-    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
-
-    sim.tc_flush_filters(bound=2, total=2)
-
-    start_test("Test if netdev removal waits for translation...")
-    delay_msec = 500
-    sim.dfs["dev/bpf_bind_verifier_delay"] = delay_msec
-    start = time.time()
-    cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
-               (sim['ifname'], obj)
-    tc_proc = cmd(cmd_line, background=True, fail=False)
-    # Wait for the verifier to start
-    while simdev.dfs_num_bound_progs() <= 2:
-        pass
-    simdev.remove()
-    end = time.time()
-    ret, _ = cmd_result(tc_proc, fail=False)
-    time_diff = end - start
-    log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
-
-    fail(ret == 0, "Managed to load TC filter on a unregistering device")
-    delay_sec = delay_msec * 0.001
-    fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
-         (time_diff, delay_sec))
-
-    # Remove all pinned files and reinstantiate the netdev
-    clean_up()
-    bpftool_prog_list_wait(expected=0)
-
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    map_obj = bpf_obj("sample_map_ret0.bpf.o")
-    start_test("Test loading program with maps...")
-    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
-
-    start_test("Test bpftool bound info reporting (own ns)...")
-    check_dev_info(False, "")
-
-    start_test("Test bpftool bound info reporting (other ns)...")
-    ns = mknetns()
-    sim.set_ns(ns)
-    check_dev_info(True, "")
-
-    start_test("Test bpftool bound info reporting (remote ns)...")
-    check_dev_info(False, ns)
-
-    start_test("Test bpftool bound info reporting (back to own ns)...")
-    sim.set_ns("")
-    check_dev_info(False, "")
-
-    prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
-    map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
-    simdev.remove()
-
-    start_test("Test bpftool bound info reporting (removed dev)...")
-    check_dev_info_removed(prog_file=prog_file, map_file=map_file)
-
-    # Remove all pinned files and reinstantiate the netdev
-    clean_up()
-    bpftool_prog_list_wait(expected=0)
-
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-
-    start_test("Test map update (no flags)...")
-    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
-    maps = bpftool_map_list(expected=2)
-    array = maps[0] if maps[0]["type"] == "array" else maps[1]
-    htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
-    for m in maps:
-        for i in range(2):
-            bpftool("map update id %d key %s value %s" %
-                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
-
-    for m in maps:
-        ret, _ = bpftool("map update id %d key %s value %s" %
-                         (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
-                         fail=False)
-        fail(ret == 0, "added too many entries")
-
-    start_test("Test map update (exists)...")
-    for m in maps:
-        for i in range(2):
-            bpftool("map update id %d key %s value %s exist" %
-                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
-
-    for m in maps:
-        ret, err = bpftool("map update id %d key %s value %s exist" %
-                           (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
-                           fail=False)
-        fail(ret == 0, "updated non-existing key")
-        fail(err["error"].find("No such file or directory") == -1,
-             "expected ENOENT, error is '%s'" % (err["error"]))
-
-    start_test("Test map update (noexist)...")
-    for m in maps:
-        for i in range(2):
-            ret, err = bpftool("map update id %d key %s value %s noexist" %
-                               (m["id"], int2str("I", i), int2str("Q", i * 3)),
-                               fail=False)
-        fail(ret == 0, "updated existing key")
-        fail(err["error"].find("File exists") == -1,
-             "expected EEXIST, error is '%s'" % (err["error"]))
-
-    start_test("Test map dump...")
-    for m in maps:
-        _, entries = bpftool("map dump id %d" % (m["id"]))
-        for i in range(2):
-            key = str2int(entries[i]["key"])
-            fail(key != i, "expected key %d, got %d" % (key, i))
-            val = str2int(entries[i]["value"])
-            fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
-
-    start_test("Test map getnext...")
-    for m in maps:
-        _, entry = bpftool("map getnext id %d" % (m["id"]))
-        key = str2int(entry["next_key"])
-        fail(key != 0, "next key %d, expected %d" % (key, 0))
-        _, entry = bpftool("map getnext id %d key %s" %
-                           (m["id"], int2str("I", 0)))
-        key = str2int(entry["next_key"])
-        fail(key != 1, "next key %d, expected %d" % (key, 1))
-        ret, err = bpftool("map getnext id %d key %s" %
-                           (m["id"], int2str("I", 1)), fail=False)
-        fail(ret == 0, "got next key past the end of map")
-        fail(err["error"].find("No such file or directory") == -1,
-             "expected ENOENT, error is '%s'" % (err["error"]))
-
-    start_test("Test map delete (htab)...")
-    for i in range(2):
-        bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
-
-    start_test("Test map delete (array)...")
-    for i in range(2):
-        ret, err = bpftool("map delete id %d key %s" %
-                           (htab["id"], int2str("I", i)), fail=False)
-        fail(ret == 0, "removed entry from an array")
-        fail(err["error"].find("No such file or directory") == -1,
-             "expected ENOENT, error is '%s'" % (err["error"]))
-
-    start_test("Test map remove...")
-    sim.unset_xdp("offload")
-    bpftool_map_list_wait(expected=0)
-    simdev.remove()
-
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
-    simdev.remove()
-    bpftool_map_list_wait(expected=0)
-
-    start_test("Test map creation fail path...")
-    simdev = NetdevSimDev()
-    sim, = simdev.nsims
-    sim.dfs["bpf_map_accept"] = "N"
-    ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
-    fail(ret == 0,
-         "netdevsim didn't refuse to create a map with offload disabled")
-
-    simdev.remove()
-
-    start_test("Test multi-dev ASIC program reuse...")
-    simdevA = NetdevSimDev()
-    simA, = simdevA.nsims
-    simdevB = NetdevSimDev(3)
-    simB1, simB2, simB3 = simdevB.nsims
-    sims = (simA, simB1, simB2, simB3)
-    simB = (simB1, simB2, simB3)
-
-    bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimA",
-                      dev=simA['ifname'])
-    progA = bpf_pinned("/sys/fs/bpf/nsimA")
-    bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB",
-                      dev=simB1['ifname'])
-    progB = bpf_pinned("/sys/fs/bpf/nsimB")
-
-    simA.set_xdp(progA, "offload", JSON=False)
-    for d in simdevB.nsims:
-        d.set_xdp(progB, "offload", JSON=False)
-
-    start_test("Test multi-dev ASIC cross-dev replace...")
-    ret, _ = simA.set_xdp(progB, "offload", force=True, JSON=False, fail=False)
-    fail(ret == 0, "cross-ASIC program allowed")
-    for d in simdevB.nsims:
-        ret, _ = d.set_xdp(progA, "offload", force=True, JSON=False, fail=False)
-        fail(ret == 0, "cross-ASIC program allowed")
-
-    start_test("Test multi-dev ASIC cross-dev install...")
-    for d in sims:
-        d.unset_xdp("offload")
-
-    ret, _, err = simA.set_xdp(progB, "offload", force=True, JSON=False,
-                               fail=False, include_stderr=True)
-    fail(ret == 0, "cross-ASIC program allowed")
-    check_extack(err, "Program bound to different device.", args)
-    for d in simdevB.nsims:
-        ret, _, err = d.set_xdp(progA, "offload", force=True, JSON=False,
-                                fail=False, include_stderr=True)
-        fail(ret == 0, "cross-ASIC program allowed")
-        check_extack(err, "Program bound to different device.", args)
-
-    start_test("Test multi-dev ASIC cross-dev map reuse...")
-
-    mapA = bpftool("prog show %s" % (progA))[1]["map_ids"][0]
-    mapB = bpftool("prog show %s" % (progB))[1]["map_ids"][0]
-
-    ret, _ = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB_",
-                               dev=simB3['ifname'],
-                               maps=["idx 0 id %d" % (mapB)],
-                               fail=False)
-    fail(ret != 0, "couldn't reuse a map on the same ASIC")
-    rm("/sys/fs/bpf/nsimB_")
-
-    ret, _, err = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimA_",
-                                    dev=simA['ifname'],
-                                    maps=["idx 0 id %d" % (mapB)],
-                                    fail=False, include_stderr=True)
-    fail(ret == 0, "could reuse a map on a different ASIC")
-    fail(err.count("offload device mismatch between prog and map") == 0,
-         "error message missing for cross-ASIC map")
-
-    ret, _, err = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB_",
-                                    dev=simB1['ifname'],
-                                    maps=["idx 0 id %d" % (mapA)],
-                                    fail=False, include_stderr=True)
-    fail(ret == 0, "could reuse a map on a different ASIC")
-    fail(err.count("offload device mismatch between prog and map") == 0,
-         "error message missing for cross-ASIC map")
-
-    start_test("Test multi-dev ASIC cross-dev destruction...")
-    bpftool_prog_list_wait(expected=2)
-
-    simdevA.remove()
-    bpftool_prog_list_wait(expected=1)
-
-    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
-    fail(ifnameB != simB1['ifname'], "program not bound to original device")
-    simB1.remove()
-    bpftool_prog_list_wait(expected=1)
-
-    start_test("Test multi-dev ASIC cross-dev destruction - move...")
-    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
-    fail(ifnameB not in (simB2['ifname'], simB3['ifname']),
-         "program not bound to remaining devices")
-
-    simB2.remove()
-    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
-    fail(ifnameB != simB3['ifname'], "program not bound to remaining device")
-
-    simB3.remove()
-    simdevB.remove()
-    bpftool_prog_list_wait(expected=0)
-
-    start_test("Test multi-dev ASIC cross-dev destruction - orphaned...")
-    ret, out = bpftool("prog show %s" % (progB), fail=False)
-    fail(ret != 0, "couldn't get information about orphaned program")
-
-    print("%s: OK" % (os.path.basename(__file__)))
-
-finally:
-    log("Clean up...", "", level=1)
-    log_level_inc()
-    clean_up()
index 5e34c93aa51b55d56b3d8d9066d5f55aa1770aae..e8bfa715aa4959f184642efade3b6b4fab26c023 100644 (file)
@@ -84,6 +84,8 @@ TEST_GEN_FILES += sctp_hello
 TEST_GEN_FILES += csum
 TEST_GEN_FILES += nat6to4.o
 TEST_GEN_FILES += xdp_dummy.o
+TEST_GEN_FILES += sample_ret0.bpf.o
+TEST_GEN_FILES += sample_map_ret0.bpf.o
 TEST_GEN_FILES += ip_local_port_range
 TEST_GEN_FILES += bind_wildcard
 TEST_PROGS += test_vxlan_mdb.sh
@@ -93,6 +95,7 @@ TEST_PROGS += test_bridge_backup_port.sh
 TEST_PROGS += fdb_flush.sh
 TEST_PROGS += fq_band_pktlimit.sh
 TEST_PROGS += vlan_hw_filter.sh
+TEST_PROGS += bpf_offload.py
 
 TEST_FILES := settings
 TEST_FILES += in_netns.sh lib.sh net_helper.sh setup_loopback.sh setup_veth.sh
@@ -142,7 +145,10 @@ endif
 
 CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG),$(CLANG_TARGET_ARCH))
 
-$(OUTPUT)/nat6to4.o $(OUTPUT)/xdp_dummy.o: $(OUTPUT)/%.o : %.c $(BPFOBJ) | $(MAKE_DIRS)
+BPF_PROG_OBJS := $(OUTPUT)/nat6to4.o $(OUTPUT)/xdp_dummy.o \
+       $(OUTPUT)/sample_map_ret0.bpf.o $(OUTPUT)/sample_ret0.bpf.o
+
+$(BPF_PROG_OBJS): $(OUTPUT)/%.o : %.c $(BPFOBJ) | $(MAKE_DIRS)
        $(CLANG) -O2 --target=bpf -c $< $(CCINCLUDE) $(CLANG_SYS_INCLUDES) -o $@
 
 $(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile)                    \
diff --git a/tools/testing/selftests/net/bpf_offload.py b/tools/testing/selftests/net/bpf_offload.py
new file mode 100755 (executable)
index 0000000..6157f88
--- /dev/null
@@ -0,0 +1,1405 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2017 Netronome Systems, Inc.
+# Copyright (c) 2019 Mellanox Technologies. All rights reserved
+#
+# This software is licensed under the GNU General License Version 2,
+# June 1991 as shown in the file COPYING in the top-level directory of this
+# source tree.
+#
+# THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
+# WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+# THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+from datetime import datetime
+import argparse
+import errno
+import json
+import os
+import pprint
+import random
+import re
+import stat
+import string
+import struct
+import subprocess
+import time
+import traceback
+
+logfile = None
+log_level = 1
+skip_extack = False
+bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
+pp = pprint.PrettyPrinter()
+devs = [] # devices we created for clean up
+files = [] # files to be removed
+netns = [] # net namespaces to be removed
+
+def log_get_sec(level=0):
+    return "*" * (log_level + level)
+
+def log_level_inc(add=1):
+    global log_level
+    log_level += add
+
+def log_level_dec(sub=1):
+    global log_level
+    log_level -= sub
+
+def log_level_set(level):
+    global log_level
+    log_level = level
+
+def log(header, data, level=None):
+    """
+    Output to an optional log.
+    """
+    if logfile is None:
+        return
+    if level is not None:
+        log_level_set(level)
+
+    if not isinstance(data, str):
+        data = pp.pformat(data)
+
+    if len(header):
+        logfile.write("\n" + log_get_sec() + " ")
+        logfile.write(header)
+    if len(header) and len(data.strip()):
+        logfile.write("\n")
+    logfile.write(data)
+
+def skip(cond, msg):
+    if not cond:
+        return
+    print("SKIP: " + msg)
+    log("SKIP: " + msg, "", level=1)
+    os.sys.exit(0)
+
+def fail(cond, msg):
+    if not cond:
+        return
+    print("FAIL: " + msg)
+    tb = "".join(traceback.extract_stack().format())
+    print(tb)
+    log("FAIL: " + msg, tb, level=1)
+    os.sys.exit(1)
+
+def start_test(msg):
+    log(msg, "", level=1)
+    log_level_inc()
+    print(msg)
+
+def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
+    """
+    Run a command in subprocess and return tuple of (retval, stdout);
+    optionally return stderr as well as third value.
+    """
+    proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
+                            stderr=subprocess.PIPE)
+    if background:
+        msg = "%s START: %s" % (log_get_sec(1),
+                                datetime.now().strftime("%H:%M:%S.%f"))
+        log("BKG " + proc.args, msg)
+        return proc
+
+    return cmd_result(proc, include_stderr=include_stderr, fail=fail)
+
+def cmd_result(proc, include_stderr=False, fail=False):
+    stdout, stderr = proc.communicate()
+    stdout = stdout.decode("utf-8")
+    stderr = stderr.decode("utf-8")
+    proc.stdout.close()
+    proc.stderr.close()
+
+    stderr = "\n" + stderr
+    if stderr[-1] == "\n":
+        stderr = stderr[:-1]
+
+    sec = log_get_sec(1)
+    log("CMD " + proc.args,
+        "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
+        (proc.returncode, sec, stdout, sec, stderr,
+         sec, datetime.now().strftime("%H:%M:%S.%f")))
+
+    if proc.returncode != 0 and fail:
+        if len(stderr) > 0 and stderr[-1] == "\n":
+            stderr = stderr[:-1]
+        raise Exception("Command failed: %s\n%s" % (proc.args, stderr))
+
+    if include_stderr:
+        return proc.returncode, stdout, stderr
+    else:
+        return proc.returncode, stdout
+
+def rm(f):
+    cmd("rm -f %s" % (f))
+    if f in files:
+        files.remove(f)
+
+def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False):
+    params = ""
+    if JSON:
+        params += "%s " % (flags["json"])
+
+    if ns != "":
+        ns = "ip netns exec %s " % (ns)
+
+    if include_stderr:
+        ret, stdout, stderr = cmd(ns + name + " " + params + args,
+                                  fail=fail, include_stderr=True)
+    else:
+        ret, stdout = cmd(ns + name + " " + params + args,
+                          fail=fail, include_stderr=False)
+
+    if JSON and len(stdout.strip()) != 0:
+        out = json.loads(stdout)
+    else:
+        out = stdout
+
+    if include_stderr:
+        return ret, out, stderr
+    else:
+        return ret, out
+
+def bpftool(args, JSON=True, ns="", fail=True, include_stderr=False):
+    return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns,
+                fail=fail, include_stderr=include_stderr)
+
+def bpftool_prog_list(expected=None, ns="", exclude_orphaned=True):
+    _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
+    # Remove the base progs
+    for p in base_progs:
+        if p in progs:
+            progs.remove(p)
+    if exclude_orphaned:
+        progs = [ p for p in progs if not p['orphaned'] ]
+    if expected is not None:
+        if len(progs) != expected:
+            fail(True, "%d BPF programs loaded, expected %d" %
+                 (len(progs), expected))
+    return progs
+
+def bpftool_map_list(expected=None, ns=""):
+    _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
+    # Remove the base maps
+    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]
+    if expected is not None:
+        if len(maps) != expected:
+            fail(True, "%d BPF maps loaded, expected %d" %
+                 (len(maps), expected))
+    return maps
+
+def bpftool_prog_list_wait(expected=0, n_retry=20):
+    for i in range(n_retry):
+        nprogs = len(bpftool_prog_list())
+        if nprogs == expected:
+            return
+        time.sleep(0.05)
+    raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
+
+def bpftool_map_list_wait(expected=0, n_retry=20):
+    for i in range(n_retry):
+        nmaps = len(bpftool_map_list())
+        if nmaps == expected:
+            return
+        time.sleep(0.05)
+    raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))
+
+def bpftool_prog_load(sample, file_name, maps=[], prog_type="xdp", dev=None,
+                      fail=True, include_stderr=False):
+    args = "prog load %s %s" % (os.path.join(bpf_test_dir, sample), file_name)
+    if prog_type is not None:
+        args += " type " + prog_type
+    if dev is not None:
+        args += " dev " + dev
+    if len(maps):
+        args += " map " + " map ".join(maps)
+
+    res = bpftool(args, fail=fail, include_stderr=include_stderr)
+    if res[0] == 0:
+        files.append(file_name)
+    return res
+
+def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False):
+    if force:
+        args = "-force " + args
+    return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns,
+                fail=fail, include_stderr=include_stderr)
+
+def tc(args, JSON=True, ns="", fail=True, include_stderr=False):
+    return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns,
+                fail=fail, include_stderr=include_stderr)
+
+def ethtool(dev, opt, args, fail=True):
+    return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)
+
+def bpf_obj(name, sec=".text", path=bpf_test_dir,):
+    return "obj %s sec %s" % (os.path.join(path, name), sec)
+
+def bpf_pinned(name):
+    return "pinned %s" % (name)
+
+def bpf_bytecode(bytecode):
+    return "bytecode \"%s\"" % (bytecode)
+
+def mknetns(n_retry=10):
+    for i in range(n_retry):
+        name = ''.join([random.choice(string.ascii_letters) for i in range(8)])
+        ret, _ = ip("netns add %s" % (name), fail=False)
+        if ret == 0:
+            netns.append(name)
+            return name
+    return None
+
+def int2str(fmt, val):
+    ret = []
+    for b in struct.pack(fmt, val):
+        ret.append(int(b))
+    return " ".join(map(lambda x: str(x), ret))
+
+def str2int(strtab):
+    inttab = []
+    for i in strtab:
+        inttab.append(int(i, 16))
+    ba = bytearray(inttab)
+    if len(strtab) == 4:
+        fmt = "I"
+    elif len(strtab) == 8:
+        fmt = "Q"
+    else:
+        raise Exception("String array of len %d can't be unpacked to an int" %
+                        (len(strtab)))
+    return struct.unpack(fmt, ba)[0]
+
+class DebugfsDir:
+    """
+    Class for accessing DebugFS directories as a dictionary.
+    """
+
+    def __init__(self, path):
+        self.path = path
+        self._dict = self._debugfs_dir_read(path)
+
+    def __len__(self):
+        return len(self._dict.keys())
+
+    def __getitem__(self, key):
+        if type(key) is int:
+            key = list(self._dict.keys())[key]
+        return self._dict[key]
+
+    def __setitem__(self, key, value):
+        log("DebugFS set %s = %s" % (key, value), "")
+        log_level_inc()
+
+        cmd("echo '%s' > %s/%s" % (value, self.path, key))
+        log_level_dec()
+
+        _, out = cmd('cat %s/%s' % (self.path, key))
+        self._dict[key] = out.strip()
+
+    def _debugfs_dir_read(self, path):
+        dfs = {}
+
+        log("DebugFS state for %s" % (path), "")
+        log_level_inc(add=2)
+
+        _, out = cmd('ls ' + path)
+        for f in out.split():
+            if f == "ports":
+                continue
+
+            p = os.path.join(path, f)
+            if not os.stat(p).st_mode & stat.S_IRUSR:
+                continue
+
+            if os.path.isfile(p):
+                # We need to init trap_flow_action_cookie before read it
+                if f == "trap_flow_action_cookie":
+                    cmd('echo deadbeef > %s/%s' % (path, f))
+                _, out = cmd('cat %s/%s' % (path, f))
+                dfs[f] = out.strip()
+            elif os.path.isdir(p):
+                dfs[f] = DebugfsDir(p)
+            else:
+                raise Exception("%s is neither file nor directory" % (p))
+
+        log_level_dec()
+        log("DebugFS state", dfs)
+        log_level_dec()
+
+        return dfs
+
+class NetdevSimDev:
+    """
+    Class for netdevsim bus device and its attributes.
+    """
+    @staticmethod
+    def ctrl_write(path, val):
+        fullpath = os.path.join("/sys/bus/netdevsim/", path)
+        try:
+            with open(fullpath, "w") as f:
+                f.write(val)
+        except OSError as e:
+            log("WRITE %s: %r" % (fullpath, val), -e.errno)
+            raise e
+        log("WRITE %s: %r" % (fullpath, val), 0)
+
+    def __init__(self, port_count=1):
+        addr = 0
+        while True:
+            try:
+                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
+            except OSError as e:
+                if e.errno == errno.ENOSPC:
+                    addr += 1
+                    continue
+                raise e
+            break
+        self.addr = addr
+
+        # As probe of netdevsim device might happen from a workqueue,
+        # so wait here until all netdevs appear.
+        self.wait_for_netdevs(port_count)
+
+        ret, out = cmd("udevadm settle", fail=False)
+        if ret:
+            raise Exception("udevadm settle failed")
+        ifnames = self.get_ifnames()
+
+        devs.append(self)
+        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
+
+        self.nsims = []
+        for port_index in range(port_count):
+            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index]))
+
+    def get_ifnames(self):
+        ifnames = []
+        listdir = os.listdir("/sys/bus/netdevsim/devices/netdevsim%u/net/" % self.addr)
+        for ifname in listdir:
+            ifnames.append(ifname)
+        ifnames.sort()
+        return ifnames
+
+    def wait_for_netdevs(self, port_count):
+        timeout = 5
+        timeout_start = time.time()
+
+        while True:
+            try:
+                ifnames = self.get_ifnames()
+            except FileNotFoundError as e:
+                ifnames = []
+            if len(ifnames) == port_count:
+                break
+            if time.time() < timeout_start + timeout:
+                continue
+            raise Exception("netdevices did not appear within timeout")
+
+    def dfs_num_bound_progs(self):
+        path = os.path.join(self.dfs_dir, "bpf_bound_progs")
+        _, progs = cmd('ls %s' % (path))
+        return len(progs.split())
+
+    def dfs_get_bound_progs(self, expected):
+        progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
+        if expected is not None:
+            if len(progs) != expected:
+                fail(True, "%d BPF programs bound, expected %d" %
+                     (len(progs), expected))
+        return progs
+
+    def remove(self):
+        self.ctrl_write("del_device", "%u" % (self.addr, ))
+        devs.remove(self)
+
+    def remove_nsim(self, nsim):
+        self.nsims.remove(nsim)
+        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
+                        "%u" % (nsim.port_index, ))
+
+class NetdevSim:
+    """
+    Class for netdevsim netdevice and its attributes.
+    """
+
+    def __init__(self, nsimdev, port_index, ifname):
+        # In case udev renamed the netdev to according to new schema,
+        # check if the name matches the port_index.
+        nsimnamere = re.compile("eni\d+np(\d+)")
+        match = nsimnamere.match(ifname)
+        if match and int(match.groups()[0]) != port_index + 1:
+            raise Exception("netdevice name mismatches the expected one")
+
+        self.nsimdev = nsimdev
+        self.port_index = port_index
+        self.ns = ""
+        self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
+        self.dfs_refresh()
+        _, [self.dev] = ip("link show dev %s" % ifname)
+
+    def __getitem__(self, key):
+        return self.dev[key]
+
+    def remove(self):
+        self.nsimdev.remove_nsim(self)
+
+    def dfs_refresh(self):
+        self.dfs = DebugfsDir(self.dfs_dir)
+        return self.dfs
+
+    def dfs_read(self, f):
+        path = os.path.join(self.dfs_dir, f)
+        _, data = cmd('cat %s' % (path))
+        return data.strip()
+
+    def wait_for_flush(self, bound=0, total=0, n_retry=20):
+        for i in range(n_retry):
+            nbound = self.nsimdev.dfs_num_bound_progs()
+            nprogs = len(bpftool_prog_list())
+            if nbound == bound and nprogs == total:
+                return
+            time.sleep(0.05)
+        raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
+
+    def set_ns(self, ns):
+        name = "1" if ns == "" else ns
+        ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
+        self.ns = ns
+
+    def set_mtu(self, mtu, fail=True):
+        return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
+                  fail=fail)
+
+    def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
+                fail=True, include_stderr=False):
+        if verbose:
+            bpf += " verbose"
+        return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
+                  force=force, JSON=JSON,
+                  fail=fail, include_stderr=include_stderr)
+
+    def unset_xdp(self, mode, force=False, JSON=True,
+                  fail=True, include_stderr=False):
+        return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
+                  force=force, JSON=JSON,
+                  fail=fail, include_stderr=include_stderr)
+
+    def ip_link_show(self, xdp):
+        _, link = ip("link show dev %s" % (self['ifname']))
+        if len(link) > 1:
+            raise Exception("Multiple objects on ip link show")
+        if len(link) < 1:
+            return {}
+        fail(xdp != "xdp" in link,
+             "XDP program not reporting in iplink (reported %s, expected %s)" %
+             ("xdp" in link, xdp))
+        return link[0]
+
+    def tc_add_ingress(self):
+        tc("qdisc add dev %s ingress" % (self['ifname']))
+
+    def tc_del_ingress(self):
+        tc("qdisc del dev %s ingress" % (self['ifname']))
+
+    def tc_flush_filters(self, bound=0, total=0):
+        self.tc_del_ingress()
+        self.tc_add_ingress()
+        self.wait_for_flush(bound=bound, total=total)
+
+    def tc_show_ingress(self, expected=None):
+        # No JSON support, oh well...
+        flags = ["skip_sw", "skip_hw", "in_hw"]
+        named = ["protocol", "pref", "chain", "handle", "id", "tag"]
+
+        args = "-s filter show dev %s ingress" % (self['ifname'])
+        _, out = tc(args, JSON=False)
+
+        filters = []
+        lines = out.split('\n')
+        for line in lines:
+            words = line.split()
+            if "handle" not in words:
+                continue
+            fltr = {}
+            for flag in flags:
+                fltr[flag] = flag in words
+            for name in named:
+                try:
+                    idx = words.index(name)
+                    fltr[name] = words[idx + 1]
+                except ValueError:
+                    pass
+            filters.append(fltr)
+
+        if expected is not None:
+            fail(len(filters) != expected,
+                 "%d ingress filters loaded, expected %d" %
+                 (len(filters), expected))
+        return filters
+
+    def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None,
+                      chain=None, cls="", params="",
+                      fail=True, include_stderr=False):
+        spec = ""
+        if prio is not None:
+            spec += " prio %d" % (prio)
+        if handle:
+            spec += " handle %s" % (handle)
+        if chain is not None:
+            spec += " chain %d" % (chain)
+
+        return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\
+                  .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec,
+                          cls=cls, params=params),
+                  fail=fail, include_stderr=include_stderr)
+
+    def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None,
+                           chain=None, da=False, verbose=False,
+                           skip_sw=False, skip_hw=False,
+                           fail=True, include_stderr=False):
+        cls = "bpf " + bpf
+
+        params = ""
+        if da:
+            params += " da"
+        if verbose:
+            params += " verbose"
+        if skip_sw:
+            params += " skip_sw"
+        if skip_hw:
+            params += " skip_hw"
+
+        return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls,
+                                  chain=chain, params=params,
+                                  fail=fail, include_stderr=include_stderr)
+
+    def set_ethtool_tc_offloads(self, enable, fail=True):
+        args = "hw-tc-offload %s" % ("on" if enable else "off")
+        return ethtool(self, "-K", args, fail=fail)
+
+################################################################################
+def clean_up():
+    global files, netns, devs
+
+    for dev in devs:
+        dev.remove()
+    for f in files:
+        cmd("rm -f %s" % (f))
+    for ns in netns:
+        cmd("ip netns delete %s" % (ns))
+    files = []
+    netns = []
+
+def pin_prog(file_name, idx=0):
+    progs = bpftool_prog_list(expected=(idx + 1))
+    prog = progs[idx]
+    bpftool("prog pin id %d %s" % (prog["id"], file_name))
+    files.append(file_name)
+
+    return file_name, bpf_pinned(file_name)
+
+def pin_map(file_name, idx=0, expected=1):
+    maps = bpftool_map_list(expected=expected)
+    m = maps[idx]
+    bpftool("map pin id %d %s" % (m["id"], file_name))
+    files.append(file_name)
+
+    return file_name, bpf_pinned(file_name)
+
+def check_dev_info_removed(prog_file=None, map_file=None):
+    bpftool_prog_list(expected=0)
+    bpftool_prog_list(expected=1, exclude_orphaned=False)
+    ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
+    fail(ret != 0, "failed to show prog with removed device")
+
+    bpftool_map_list(expected=0)
+    ret, err = bpftool("map show pin %s" % (map_file), fail=False)
+    fail(ret == 0, "Showing map with removed device did not fail")
+    fail(err["error"].find("No such device") == -1,
+         "Showing map with removed device expected ENODEV, error is %s" %
+         (err["error"]))
+
+def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
+    progs = bpftool_prog_list(expected=1, ns=ns)
+    prog = progs[0]
+
+    fail("dev" not in prog.keys(), "Device parameters not reported")
+    dev = prog["dev"]
+    fail("ifindex" not in dev.keys(), "Device parameters not reported")
+    fail("ns_dev" not in dev.keys(), "Device parameters not reported")
+    fail("ns_inode" not in dev.keys(), "Device parameters not reported")
+
+    if not other_ns:
+        fail("ifname" not in dev.keys(), "Ifname not reported")
+        fail(dev["ifname"] != sim["ifname"],
+             "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
+    else:
+        fail("ifname" in dev.keys(), "Ifname is reported for other ns")
+
+    maps = bpftool_map_list(expected=2, ns=ns)
+    for m in maps:
+        fail("dev" not in m.keys(), "Device parameters not reported")
+        fail(dev != m["dev"], "Map's device different than program's")
+
+def check_extack(output, reference, args):
+    if skip_extack:
+        return
+    lines = output.split("\n")
+    comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference
+    fail(not comp, "Missing or incorrect netlink extack message")
+
+def check_extack_nsim(output, reference, args):
+    check_extack(output, "netdevsim: " + reference, args)
+
+def check_no_extack(res, needle):
+    fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"),
+         "Found '%s' in command output, leaky extack?" % (needle))
+
+def check_verifier_log(output, reference):
+    lines = output.split("\n")
+    for l in reversed(lines):
+        if l == reference:
+            return
+    fail(True, "Missing or incorrect message from netdevsim in verifier log")
+
+def check_multi_basic(two_xdps):
+    fail(two_xdps["mode"] != 4, "Bad mode reported with multiple programs")
+    fail("prog" in two_xdps, "Base program reported in multi program mode")
+    fail(len(two_xdps["attached"]) != 2,
+         "Wrong attached program count with two programs")
+    fail(two_xdps["attached"][0]["prog"]["id"] ==
+         two_xdps["attached"][1]["prog"]["id"],
+         "Offloaded and other programs have the same id")
+
+def test_spurios_extack(sim, obj, skip_hw, needle):
+    res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw,
+                                 include_stderr=True)
+    check_no_extack(res, needle)
+    res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
+                                 skip_hw=skip_hw, include_stderr=True)
+    check_no_extack(res, needle)
+    res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf",
+                            include_stderr=True)
+    check_no_extack(res, needle)
+
+def test_multi_prog(simdev, sim, obj, modename, modeid):
+    start_test("Test multi-attachment XDP - %s + offload..." %
+               (modename or "default", ))
+    sim.set_xdp(obj, "offload")
+    xdp = sim.ip_link_show(xdp=True)["xdp"]
+    offloaded = sim.dfs_read("bpf_offloaded_id")
+    fail("prog" not in xdp, "Base program not reported in single program mode")
+    fail(len(xdp["attached"]) != 1,
+         "Wrong attached program count with one program")
+
+    sim.set_xdp(obj, modename)
+    two_xdps = sim.ip_link_show(xdp=True)["xdp"]
+
+    fail(xdp["attached"][0] not in two_xdps["attached"],
+         "Offload program not reported after other activated")
+    check_multi_basic(two_xdps)
+
+    offloaded2 = sim.dfs_read("bpf_offloaded_id")
+    fail(offloaded != offloaded2,
+         "Offload ID changed after loading other program")
+
+    start_test("Test multi-attachment XDP - replace...")
+    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
+    fail(ret == 0, "Replaced one of programs without -force")
+    check_extack(err, "XDP program already attached.", args)
+
+    start_test("Test multi-attachment XDP - remove without mode...")
+    ret, _, err = sim.unset_xdp("", force=True,
+                                fail=False, include_stderr=True)
+    fail(ret == 0, "Removed program without a mode flag")
+    check_extack(err, "More than one program loaded, unset mode is ambiguous.", args)
+
+    sim.unset_xdp("offload")
+    xdp = sim.ip_link_show(xdp=True)["xdp"]
+    offloaded = sim.dfs_read("bpf_offloaded_id")
+
+    fail(xdp["mode"] != modeid, "Bad mode reported after multiple programs")
+    fail("prog" not in xdp,
+         "Base program not reported after multi program mode")
+    fail(xdp["attached"][0] not in two_xdps["attached"],
+         "Offload program not reported after other activated")
+    fail(len(xdp["attached"]) != 1,
+         "Wrong attached program count with remaining programs")
+    fail(offloaded != "0", "Offload ID reported with only other program left")
+
+    start_test("Test multi-attachment XDP - reattach...")
+    sim.set_xdp(obj, "offload")
+    two_xdps = sim.ip_link_show(xdp=True)["xdp"]
+
+    fail(xdp["attached"][0] not in two_xdps["attached"],
+         "Other program not reported after offload activated")
+    check_multi_basic(two_xdps)
+
+    start_test("Test multi-attachment XDP - device remove...")
+    simdev.remove()
+
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    sim.set_ethtool_tc_offloads(True)
+    return [simdev, sim]
+
+# Parse command line
+parser = argparse.ArgumentParser()
+parser.add_argument("--log", help="output verbose log to given file")
+args = parser.parse_args()
+if args.log:
+    logfile = open(args.log, 'w+')
+    logfile.write("# -*-Org-*-")
+
+log("Prepare...", "", level=1)
+log_level_inc()
+
+# Check permissions
+skip(os.getuid() != 0, "test must be run as root")
+
+# Check tools
+ret, progs = bpftool("prog", fail=False)
+skip(ret != 0, "bpftool not installed")
+base_progs = progs
+_, base_maps = bpftool("map")
+base_map_names = [
+    'pid_iter.rodata', # created on each bpftool invocation
+    'libbpf_det_bind', # created on each bpftool invocation
+]
+
+# Check netdevsim
+if not os.path.isdir("/sys/bus/netdevsim/"):
+    ret, out = cmd("modprobe netdevsim", fail=False)
+    skip(ret != 0, "netdevsim module could not be loaded")
+
+# Check debugfs
+_, out = cmd("mount")
+if out.find("/sys/kernel/debug type debugfs") == -1:
+    cmd("mount -t debugfs none /sys/kernel/debug")
+
+# Check samples are compiled
+samples = ["sample_ret0.bpf.o", "sample_map_ret0.bpf.o"]
+for s in samples:
+    ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
+    skip(ret != 0, "sample %s/%s not found, please compile it" %
+         (bpf_test_dir, s))
+
+# Check if iproute2 is built with libmnl (needed by extack support)
+_, _, err = cmd("tc qdisc delete dev lo handle 0",
+                fail=False, include_stderr=True)
+if err.find("Error: Failed to find qdisc with specified handle.") == -1:
+    print("Warning: no extack message in iproute2 output, libmnl missing?")
+    log("Warning: no extack message in iproute2 output, libmnl missing?", "")
+    skip_extack = True
+
+# Check if net namespaces seem to work
+ns = mknetns()
+skip(ns is None, "Could not create a net namespace")
+cmd("ip netns delete %s" % (ns))
+netns = []
+
+try:
+    obj = bpf_obj("sample_ret0.bpf.o")
+    bytecode = bpf_bytecode("1,6 0 0 4294967295,")
+
+    start_test("Test destruction of generic XDP...")
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    sim.set_xdp(obj, "generic")
+    simdev.remove()
+    bpftool_prog_list_wait(expected=0)
+
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    sim.tc_add_ingress()
+
+    start_test("Test TC non-offloaded...")
+    ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
+    fail(ret != 0, "Software TC filter did not load")
+
+    start_test("Test TC non-offloaded isn't getting bound...")
+    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
+    fail(ret != 0, "Software TC filter did not load")
+    simdev.dfs_get_bound_progs(expected=0)
+
+    sim.tc_flush_filters()
+
+    start_test("Test TC offloads are off by default...")
+    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
+                                         fail=False, include_stderr=True)
+    fail(ret == 0, "TC filter loaded without enabling TC offloads")
+    check_extack(err, "TC offload is disabled on net device.", args)
+    sim.wait_for_flush()
+
+    sim.set_ethtool_tc_offloads(True)
+    sim.dfs["bpf_tc_non_bound_accept"] = "Y"
+
+    start_test("Test TC offload by default...")
+    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
+    fail(ret != 0, "Software TC filter did not load")
+    simdev.dfs_get_bound_progs(expected=0)
+    ingress = sim.tc_show_ingress(expected=1)
+    fltr = ingress[0]
+    fail(not fltr["in_hw"], "Filter not offloaded by default")
+
+    sim.tc_flush_filters()
+
+    start_test("Test TC cBPF bytcode tries offload by default...")
+    ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
+    fail(ret != 0, "Software TC filter did not load")
+    simdev.dfs_get_bound_progs(expected=0)
+    ingress = sim.tc_show_ingress(expected=1)
+    fltr = ingress[0]
+    fail(not fltr["in_hw"], "Bytecode not offloaded by default")
+
+    sim.tc_flush_filters()
+    sim.dfs["bpf_tc_non_bound_accept"] = "N"
+
+    start_test("Test TC cBPF unbound bytecode doesn't offload...")
+    ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
+                                         fail=False, include_stderr=True)
+    fail(ret == 0, "TC bytecode loaded for offload")
+    check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
+                      args)
+    sim.wait_for_flush()
+
+    start_test("Test non-0 chain offload...")
+    ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1,
+                                         skip_sw=True,
+                                         fail=False, include_stderr=True)
+    fail(ret == 0, "Offloaded a filter to chain other than 0")
+    check_extack(err, "Driver supports only offload of chain 0.", args)
+    sim.tc_flush_filters()
+
+    start_test("Test TC replace...")
+    sim.cls_bpf_add_filter(obj, prio=1, handle=1)
+    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1)
+    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
+
+    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True)
+    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True)
+    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
+
+    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True)
+    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True)
+    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
+
+    start_test("Test TC replace bad flags...")
+    for i in range(3):
+        for j in range(3):
+            ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
+                                            skip_sw=(j == 1), skip_hw=(j == 2),
+                                            fail=False)
+            fail(bool(ret) != bool(j),
+                 "Software TC incorrect load in replace test, iteration %d" %
+                 (j))
+        sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
+
+    start_test("Test spurious extack from the driver...")
+    test_spurios_extack(sim, obj, False, "netdevsim")
+    test_spurios_extack(sim, obj, True, "netdevsim")
+
+    sim.set_ethtool_tc_offloads(False)
+
+    test_spurios_extack(sim, obj, False, "TC offload is disabled")
+    test_spurios_extack(sim, obj, True, "TC offload is disabled")
+
+    sim.set_ethtool_tc_offloads(True)
+
+    sim.tc_flush_filters()
+
+    start_test("Test TC offloads failure...")
+    sim.dfs["dev/bpf_bind_verifier_accept"] = 0
+    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
+                                         fail=False, include_stderr=True)
+    fail(ret == 0, "TC filter did not reject with TC offloads enabled")
+    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
+    sim.dfs["dev/bpf_bind_verifier_accept"] = 1
+
+    start_test("Test TC offloads work...")
+    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
+                                         fail=False, include_stderr=True)
+    fail(ret != 0, "TC filter did not load with TC offloads enabled")
+
+    start_test("Test TC offload basics...")
+    dfs = simdev.dfs_get_bound_progs(expected=1)
+    progs = bpftool_prog_list(expected=1)
+    ingress = sim.tc_show_ingress(expected=1)
+
+    dprog = dfs[0]
+    prog = progs[0]
+    fltr = ingress[0]
+    fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
+    fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
+    fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
+
+    start_test("Test TC offload is device-bound...")
+    fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
+    fail(prog["tag"] != fltr["tag"], "Program tags don't match")
+    fail(fltr["id"] != dprog["id"], "Program IDs don't match")
+    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
+    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
+
+    start_test("Test disabling TC offloads is rejected while filters installed...")
+    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
+    fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
+    sim.set_ethtool_tc_offloads(True)
+
+    start_test("Test qdisc removal frees things...")
+    sim.tc_flush_filters()
+    sim.tc_show_ingress(expected=0)
+
+    start_test("Test disabling TC offloads is OK without filters...")
+    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
+    fail(ret != 0,
+         "Driver refused to disable TC offloads without filters installed...")
+
+    sim.set_ethtool_tc_offloads(True)
+
+    start_test("Test destroying device gets rid of TC filters...")
+    sim.cls_bpf_add_filter(obj, skip_sw=True)
+    simdev.remove()
+    bpftool_prog_list_wait(expected=0)
+
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    sim.set_ethtool_tc_offloads(True)
+
+    start_test("Test destroying device gets rid of XDP...")
+    sim.set_xdp(obj, "offload")
+    simdev.remove()
+    bpftool_prog_list_wait(expected=0)
+
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    sim.set_ethtool_tc_offloads(True)
+
+    start_test("Test XDP prog reporting...")
+    sim.set_xdp(obj, "drv")
+    ipl = sim.ip_link_show(xdp=True)
+    progs = bpftool_prog_list(expected=1)
+    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
+         "Loaded program has wrong ID")
+
+    start_test("Test XDP prog replace without force...")
+    ret, _ = sim.set_xdp(obj, "drv", fail=False)
+    fail(ret == 0, "Replaced XDP program without -force")
+    sim.wait_for_flush(total=1)
+
+    start_test("Test XDP prog replace with force...")
+    ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
+    fail(ret != 0, "Could not replace XDP program with -force")
+    bpftool_prog_list_wait(expected=1)
+    ipl = sim.ip_link_show(xdp=True)
+    progs = bpftool_prog_list(expected=1)
+    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
+         "Loaded program has wrong ID")
+    fail("dev" in progs[0].keys(),
+         "Device parameters reported for non-offloaded program")
+
+    start_test("Test XDP prog replace with bad flags...")
+    ret, _, err = sim.set_xdp(obj, "generic", force=True,
+                              fail=False, include_stderr=True)
+    fail(ret == 0, "Replaced XDP program with a program in different mode")
+    check_extack(err,
+                 "Native and generic XDP can't be active at the same time.",
+                 args)
+
+    start_test("Test MTU restrictions...")
+    ret, _ = sim.set_mtu(9000, fail=False)
+    fail(ret == 0,
+         "Driver should refuse to increase MTU to 9000 with XDP loaded...")
+    sim.unset_xdp("drv")
+    bpftool_prog_list_wait(expected=0)
+    sim.set_mtu(9000)
+    ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True)
+    fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
+    check_extack_nsim(err, "MTU too large w/ XDP enabled.", args)
+    sim.set_mtu(1500)
+
+    sim.wait_for_flush()
+    start_test("Test non-offload XDP attaching to HW...")
+    bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/nooffload")
+    nooffload = bpf_pinned("/sys/fs/bpf/nooffload")
+    ret, _, err = sim.set_xdp(nooffload, "offload",
+                              fail=False, include_stderr=True)
+    fail(ret == 0, "attached non-offloaded XDP program to HW")
+    check_extack_nsim(err, "xdpoffload of non-bound program.", args)
+    rm("/sys/fs/bpf/nooffload")
+
+    start_test("Test offload XDP attaching to drv...")
+    bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/offload",
+                      dev=sim['ifname'])
+    offload = bpf_pinned("/sys/fs/bpf/offload")
+    ret, _, err = sim.set_xdp(offload, "drv", fail=False, include_stderr=True)
+    fail(ret == 0, "attached offloaded XDP program to drv")
+    check_extack(err, "Using offloaded program without HW_MODE flag is not supported.", args)
+    rm("/sys/fs/bpf/offload")
+    sim.wait_for_flush()
+
+    start_test("Test XDP load failure...")
+    sim.dfs["dev/bpf_bind_verifier_accept"] = 0
+    ret, _, err = bpftool_prog_load("sample_ret0.bpf.o", "/sys/fs/bpf/offload",
+                                 dev=sim['ifname'], fail=False, include_stderr=True)
+    fail(ret == 0, "verifier should fail on load")
+    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
+    sim.dfs["dev/bpf_bind_verifier_accept"] = 1
+    sim.wait_for_flush()
+
+    start_test("Test XDP offload...")
+    _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
+    ipl = sim.ip_link_show(xdp=True)
+    link_xdp = ipl["xdp"]["prog"]
+    progs = bpftool_prog_list(expected=1)
+    prog = progs[0]
+    fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
+
+    start_test("Test XDP offload is device bound...")
+    dfs = simdev.dfs_get_bound_progs(expected=1)
+    dprog = dfs[0]
+
+    fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
+    fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
+    fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
+    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
+    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
+
+    start_test("Test removing XDP program many times...")
+    sim.unset_xdp("offload")
+    sim.unset_xdp("offload")
+    sim.unset_xdp("drv")
+    sim.unset_xdp("drv")
+    sim.unset_xdp("")
+    sim.unset_xdp("")
+    bpftool_prog_list_wait(expected=0)
+
+    start_test("Test attempt to use a program for a wrong device...")
+    simdev2 = NetdevSimDev()
+    sim2, = simdev2.nsims
+    sim2.set_xdp(obj, "offload")
+    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
+
+    ret, _, err = sim.set_xdp(pinned, "offload",
+                              fail=False, include_stderr=True)
+    fail(ret == 0, "Pinned program loaded for a different device accepted")
+    check_extack(err, "Program bound to different device.", args)
+    simdev2.remove()
+    ret, _, err = sim.set_xdp(pinned, "offload",
+                              fail=False, include_stderr=True)
+    fail(ret == 0, "Pinned program loaded for a removed device accepted")
+    check_extack(err, "Program bound to different device.", args)
+    rm(pin_file)
+    bpftool_prog_list_wait(expected=0)
+
+    simdev, sim = test_multi_prog(simdev, sim, obj, "", 1)
+    simdev, sim = test_multi_prog(simdev, sim, obj, "drv", 1)
+    simdev, sim = test_multi_prog(simdev, sim, obj, "generic", 2)
+
+    start_test("Test mixing of TC and XDP...")
+    sim.tc_add_ingress()
+    sim.set_xdp(obj, "offload")
+    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
+                                         fail=False, include_stderr=True)
+    fail(ret == 0, "Loading TC when XDP active should fail")
+    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
+    sim.unset_xdp("offload")
+    sim.wait_for_flush()
+
+    sim.cls_bpf_add_filter(obj, skip_sw=True)
+    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
+    fail(ret == 0, "Loading XDP when TC active should fail")
+    check_extack_nsim(err, "TC program is already loaded.", args)
+
+    start_test("Test binding TC from pinned...")
+    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
+    sim.tc_flush_filters(bound=1, total=1)
+    sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
+    sim.tc_flush_filters(bound=1, total=1)
+
+    start_test("Test binding XDP from pinned...")
+    sim.set_xdp(obj, "offload")
+    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
+
+    sim.set_xdp(pinned, "offload", force=True)
+    sim.unset_xdp("offload")
+    sim.set_xdp(pinned, "offload", force=True)
+    sim.unset_xdp("offload")
+
+    start_test("Test offload of wrong type fails...")
+    ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
+    fail(ret == 0, "Managed to attach XDP program to TC")
+
+    start_test("Test asking for TC offload of two filters...")
+    sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
+    ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
+                                         fail=False, include_stderr=True)
+    fail(ret == 0, "Managed to offload two TC filters at the same time")
+    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
+
+    sim.tc_flush_filters(bound=2, total=2)
+
+    start_test("Test if netdev removal waits for translation...")
+    delay_msec = 500
+    sim.dfs["dev/bpf_bind_verifier_delay"] = delay_msec
+    start = time.time()
+    cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
+               (sim['ifname'], obj)
+    tc_proc = cmd(cmd_line, background=True, fail=False)
+    # Wait for the verifier to start
+    while simdev.dfs_num_bound_progs() <= 2:
+        pass
+    simdev.remove()
+    end = time.time()
+    ret, _ = cmd_result(tc_proc, fail=False)
+    time_diff = end - start
+    log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
+
+    fail(ret == 0, "Managed to load TC filter on a unregistering device")
+    delay_sec = delay_msec * 0.001
+    fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
+         (time_diff, delay_sec))
+
+    # Remove all pinned files and reinstantiate the netdev
+    clean_up()
+    bpftool_prog_list_wait(expected=0)
+
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    map_obj = bpf_obj("sample_map_ret0.bpf.o")
+    start_test("Test loading program with maps...")
+    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
+
+    start_test("Test bpftool bound info reporting (own ns)...")
+    check_dev_info(False, "")
+
+    start_test("Test bpftool bound info reporting (other ns)...")
+    ns = mknetns()
+    sim.set_ns(ns)
+    check_dev_info(True, "")
+
+    start_test("Test bpftool bound info reporting (remote ns)...")
+    check_dev_info(False, ns)
+
+    start_test("Test bpftool bound info reporting (back to own ns)...")
+    sim.set_ns("")
+    check_dev_info(False, "")
+
+    prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
+    map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
+    simdev.remove()
+
+    start_test("Test bpftool bound info reporting (removed dev)...")
+    check_dev_info_removed(prog_file=prog_file, map_file=map_file)
+
+    # Remove all pinned files and reinstantiate the netdev
+    clean_up()
+    bpftool_prog_list_wait(expected=0)
+
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+
+    start_test("Test map update (no flags)...")
+    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
+    maps = bpftool_map_list(expected=2)
+    array = maps[0] if maps[0]["type"] == "array" else maps[1]
+    htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
+    for m in maps:
+        for i in range(2):
+            bpftool("map update id %d key %s value %s" %
+                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
+
+    for m in maps:
+        ret, _ = bpftool("map update id %d key %s value %s" %
+                         (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
+                         fail=False)
+        fail(ret == 0, "added too many entries")
+
+    start_test("Test map update (exists)...")
+    for m in maps:
+        for i in range(2):
+            bpftool("map update id %d key %s value %s exist" %
+                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
+
+    for m in maps:
+        ret, err = bpftool("map update id %d key %s value %s exist" %
+                           (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
+                           fail=False)
+        fail(ret == 0, "updated non-existing key")
+        fail(err["error"].find("No such file or directory") == -1,
+             "expected ENOENT, error is '%s'" % (err["error"]))
+
+    start_test("Test map update (noexist)...")
+    for m in maps:
+        for i in range(2):
+            ret, err = bpftool("map update id %d key %s value %s noexist" %
+                               (m["id"], int2str("I", i), int2str("Q", i * 3)),
+                               fail=False)
+        fail(ret == 0, "updated existing key")
+        fail(err["error"].find("File exists") == -1,
+             "expected EEXIST, error is '%s'" % (err["error"]))
+
+    start_test("Test map dump...")
+    for m in maps:
+        _, entries = bpftool("map dump id %d" % (m["id"]))
+        for i in range(2):
+            key = str2int(entries[i]["key"])
+            fail(key != i, "expected key %d, got %d" % (key, i))
+            val = str2int(entries[i]["value"])
+            fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
+
+    start_test("Test map getnext...")
+    for m in maps:
+        _, entry = bpftool("map getnext id %d" % (m["id"]))
+        key = str2int(entry["next_key"])
+        fail(key != 0, "next key %d, expected %d" % (key, 0))
+        _, entry = bpftool("map getnext id %d key %s" %
+                           (m["id"], int2str("I", 0)))
+        key = str2int(entry["next_key"])
+        fail(key != 1, "next key %d, expected %d" % (key, 1))
+        ret, err = bpftool("map getnext id %d key %s" %
+                           (m["id"], int2str("I", 1)), fail=False)
+        fail(ret == 0, "got next key past the end of map")
+        fail(err["error"].find("No such file or directory") == -1,
+             "expected ENOENT, error is '%s'" % (err["error"]))
+
+    start_test("Test map delete (htab)...")
+    for i in range(2):
+        bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
+
+    start_test("Test map delete (array)...")
+    for i in range(2):
+        ret, err = bpftool("map delete id %d key %s" %
+                           (htab["id"], int2str("I", i)), fail=False)
+        fail(ret == 0, "removed entry from an array")
+        fail(err["error"].find("No such file or directory") == -1,
+             "expected ENOENT, error is '%s'" % (err["error"]))
+
+    start_test("Test map remove...")
+    sim.unset_xdp("offload")
+    bpftool_map_list_wait(expected=0)
+    simdev.remove()
+
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
+    simdev.remove()
+    bpftool_map_list_wait(expected=0)
+
+    start_test("Test map creation fail path...")
+    simdev = NetdevSimDev()
+    sim, = simdev.nsims
+    sim.dfs["bpf_map_accept"] = "N"
+    ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
+    fail(ret == 0,
+         "netdevsim didn't refuse to create a map with offload disabled")
+
+    simdev.remove()
+
+    start_test("Test multi-dev ASIC program reuse...")
+    simdevA = NetdevSimDev()
+    simA, = simdevA.nsims
+    simdevB = NetdevSimDev(3)
+    simB1, simB2, simB3 = simdevB.nsims
+    sims = (simA, simB1, simB2, simB3)
+    simB = (simB1, simB2, simB3)
+
+    bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimA",
+                      dev=simA['ifname'])
+    progA = bpf_pinned("/sys/fs/bpf/nsimA")
+    bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB",
+                      dev=simB1['ifname'])
+    progB = bpf_pinned("/sys/fs/bpf/nsimB")
+
+    simA.set_xdp(progA, "offload", JSON=False)
+    for d in simdevB.nsims:
+        d.set_xdp(progB, "offload", JSON=False)
+
+    start_test("Test multi-dev ASIC cross-dev replace...")
+    ret, _ = simA.set_xdp(progB, "offload", force=True, JSON=False, fail=False)
+    fail(ret == 0, "cross-ASIC program allowed")
+    for d in simdevB.nsims:
+        ret, _ = d.set_xdp(progA, "offload", force=True, JSON=False, fail=False)
+        fail(ret == 0, "cross-ASIC program allowed")
+
+    start_test("Test multi-dev ASIC cross-dev install...")
+    for d in sims:
+        d.unset_xdp("offload")
+
+    ret, _, err = simA.set_xdp(progB, "offload", force=True, JSON=False,
+                               fail=False, include_stderr=True)
+    fail(ret == 0, "cross-ASIC program allowed")
+    check_extack(err, "Program bound to different device.", args)
+    for d in simdevB.nsims:
+        ret, _, err = d.set_xdp(progA, "offload", force=True, JSON=False,
+                                fail=False, include_stderr=True)
+        fail(ret == 0, "cross-ASIC program allowed")
+        check_extack(err, "Program bound to different device.", args)
+
+    start_test("Test multi-dev ASIC cross-dev map reuse...")
+
+    mapA = bpftool("prog show %s" % (progA))[1]["map_ids"][0]
+    mapB = bpftool("prog show %s" % (progB))[1]["map_ids"][0]
+
+    ret, _ = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB_",
+                               dev=simB3['ifname'],
+                               maps=["idx 0 id %d" % (mapB)],
+                               fail=False)
+    fail(ret != 0, "couldn't reuse a map on the same ASIC")
+    rm("/sys/fs/bpf/nsimB_")
+
+    ret, _, err = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimA_",
+                                    dev=simA['ifname'],
+                                    maps=["idx 0 id %d" % (mapB)],
+                                    fail=False, include_stderr=True)
+    fail(ret == 0, "could reuse a map on a different ASIC")
+    fail(err.count("offload device mismatch between prog and map") == 0,
+         "error message missing for cross-ASIC map")
+
+    ret, _, err = bpftool_prog_load("sample_map_ret0.bpf.o", "/sys/fs/bpf/nsimB_",
+                                    dev=simB1['ifname'],
+                                    maps=["idx 0 id %d" % (mapA)],
+                                    fail=False, include_stderr=True)
+    fail(ret == 0, "could reuse a map on a different ASIC")
+    fail(err.count("offload device mismatch between prog and map") == 0,
+         "error message missing for cross-ASIC map")
+
+    start_test("Test multi-dev ASIC cross-dev destruction...")
+    bpftool_prog_list_wait(expected=2)
+
+    simdevA.remove()
+    bpftool_prog_list_wait(expected=1)
+
+    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
+    fail(ifnameB != simB1['ifname'], "program not bound to original device")
+    simB1.remove()
+    bpftool_prog_list_wait(expected=1)
+
+    start_test("Test multi-dev ASIC cross-dev destruction - move...")
+    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
+    fail(ifnameB not in (simB2['ifname'], simB3['ifname']),
+         "program not bound to remaining devices")
+
+    simB2.remove()
+    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
+    fail(ifnameB != simB3['ifname'], "program not bound to remaining device")
+
+    simB3.remove()
+    simdevB.remove()
+    bpftool_prog_list_wait(expected=0)
+
+    start_test("Test multi-dev ASIC cross-dev destruction - orphaned...")
+    ret, out = bpftool("prog show %s" % (progB), fail=False)
+    fail(ret != 0, "couldn't get information about orphaned program")
+
+    print("%s: OK" % (os.path.basename(__file__)))
+
+finally:
+    log("Clean up...", "", level=1)
+    log_level_inc()
+    clean_up()
diff --git a/tools/testing/selftests/net/sample_map_ret0.bpf.c b/tools/testing/selftests/net/sample_map_ret0.bpf.c
new file mode 100644 (file)
index 0000000..495990d
--- /dev/null
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+       __uint(type, BPF_MAP_TYPE_HASH);
+       __type(key, __u32);
+       __type(value, long);
+       __uint(max_entries, 2);
+} htab SEC(".maps");
+
+struct {
+       __uint(type, BPF_MAP_TYPE_ARRAY);
+       __type(key, __u32);
+       __type(value, long);
+       __uint(max_entries, 2);
+} array SEC(".maps");
+
+/* Sample program which should always load for testing control paths. */
+SEC(".text") int func()
+{
+       __u64 key64 = 0;
+       __u32 key = 0;
+       long *value;
+
+       value = bpf_map_lookup_elem(&htab, &key);
+       if (!value)
+               return 1;
+       value = bpf_map_lookup_elem(&array, &key64);
+       if (!value)
+               return 1;
+
+       return 0;
+}
diff --git a/tools/testing/selftests/net/sample_ret0.bpf.c b/tools/testing/selftests/net/sample_ret0.bpf.c
new file mode 100644 (file)
index 0000000..fec9975
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
+
+/* Sample program which should always load for testing control paths. */
+int func()
+{
+       return 0;
+}