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