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