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