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