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