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