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