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