xref: /openbmc/linux/tools/testing/selftests/bpf/test_offload.py (revision a0ae2562c6c4b2721d9fddba63b7286c13517d9f)
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_read(self, f):
343        path = os.path.join(self.dfs_dir, f)
344        _, data = cmd('cat %s' % (path))
345        return data.strip()
346
347    def dfs_num_bound_progs(self):
348        path = os.path.join(self.dfs_dir, "bpf_bound_progs")
349        _, progs = cmd('ls %s' % (path))
350        return len(progs.split())
351
352    def dfs_get_bound_progs(self, expected):
353        progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
354        if expected is not None:
355            if len(progs) != expected:
356                fail(True, "%d BPF programs bound, expected %d" %
357                     (len(progs), expected))
358        return progs
359
360    def wait_for_flush(self, bound=0, total=0, n_retry=20):
361        for i in range(n_retry):
362            nbound = self.dfs_num_bound_progs()
363            nprogs = len(bpftool_prog_list())
364            if nbound == bound and nprogs == total:
365                return
366            time.sleep(0.05)
367        raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
368
369    def set_ns(self, ns):
370        name = "1" if ns == "" else ns
371        ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
372        self.ns = ns
373
374    def set_mtu(self, mtu, fail=True):
375        return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
376                  fail=fail)
377
378    def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
379                fail=True, include_stderr=False):
380        if verbose:
381            bpf += " verbose"
382        return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
383                  force=force, JSON=JSON,
384                  fail=fail, include_stderr=include_stderr)
385
386    def unset_xdp(self, mode, force=False, JSON=True,
387                  fail=True, include_stderr=False):
388        return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
389                  force=force, JSON=JSON,
390                  fail=fail, include_stderr=include_stderr)
391
392    def ip_link_show(self, xdp):
393        _, link = ip("link show dev %s" % (self['ifname']))
394        if len(link) > 1:
395            raise Exception("Multiple objects on ip link show")
396        if len(link) < 1:
397            return {}
398        fail(xdp != "xdp" in link,
399             "XDP program not reporting in iplink (reported %s, expected %s)" %
400             ("xdp" in link, xdp))
401        return link[0]
402
403    def tc_add_ingress(self):
404        tc("qdisc add dev %s ingress" % (self['ifname']))
405
406    def tc_del_ingress(self):
407        tc("qdisc del dev %s ingress" % (self['ifname']))
408
409    def tc_flush_filters(self, bound=0, total=0):
410        self.tc_del_ingress()
411        self.tc_add_ingress()
412        self.wait_for_flush(bound=bound, total=total)
413
414    def tc_show_ingress(self, expected=None):
415        # No JSON support, oh well...
416        flags = ["skip_sw", "skip_hw", "in_hw"]
417        named = ["protocol", "pref", "chain", "handle", "id", "tag"]
418
419        args = "-s filter show dev %s ingress" % (self['ifname'])
420        _, out = tc(args, JSON=False)
421
422        filters = []
423        lines = out.split('\n')
424        for line in lines:
425            words = line.split()
426            if "handle" not in words:
427                continue
428            fltr = {}
429            for flag in flags:
430                fltr[flag] = flag in words
431            for name in named:
432                try:
433                    idx = words.index(name)
434                    fltr[name] = words[idx + 1]
435                except ValueError:
436                    pass
437            filters.append(fltr)
438
439        if expected is not None:
440            fail(len(filters) != expected,
441                 "%d ingress filters loaded, expected %d" %
442                 (len(filters), expected))
443        return filters
444
445    def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None,
446                      chain=None, cls="", params="",
447                      fail=True, include_stderr=False):
448        spec = ""
449        if prio is not None:
450            spec += " prio %d" % (prio)
451        if handle:
452            spec += " handle %s" % (handle)
453        if chain is not None:
454            spec += " chain %d" % (chain)
455
456        return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\
457                  .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec,
458                          cls=cls, params=params),
459                  fail=fail, include_stderr=include_stderr)
460
461    def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None,
462                           chain=None, da=False, verbose=False,
463                           skip_sw=False, skip_hw=False,
464                           fail=True, include_stderr=False):
465        cls = "bpf " + bpf
466
467        params = ""
468        if da:
469            params += " da"
470        if verbose:
471            params += " verbose"
472        if skip_sw:
473            params += " skip_sw"
474        if skip_hw:
475            params += " skip_hw"
476
477        return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls,
478                                  chain=chain, params=params,
479                                  fail=fail, include_stderr=include_stderr)
480
481    def set_ethtool_tc_offloads(self, enable, fail=True):
482        args = "hw-tc-offload %s" % ("on" if enable else "off")
483        return ethtool(self, "-K", args, fail=fail)
484
485################################################################################
486def clean_up():
487    global files, netns, devs
488
489    for dev in devs:
490        dev.remove()
491    for f in files:
492        cmd("rm -f %s" % (f))
493    for ns in netns:
494        cmd("ip netns delete %s" % (ns))
495    files = []
496    netns = []
497
498def pin_prog(file_name, idx=0):
499    progs = bpftool_prog_list(expected=(idx + 1))
500    prog = progs[idx]
501    bpftool("prog pin id %d %s" % (prog["id"], file_name))
502    files.append(file_name)
503
504    return file_name, bpf_pinned(file_name)
505
506def pin_map(file_name, idx=0, expected=1):
507    maps = bpftool_map_list(expected=expected)
508    m = maps[idx]
509    bpftool("map pin id %d %s" % (m["id"], file_name))
510    files.append(file_name)
511
512    return file_name, bpf_pinned(file_name)
513
514def check_dev_info_removed(prog_file=None, map_file=None):
515    bpftool_prog_list(expected=0)
516    ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
517    fail(ret == 0, "Showing prog with removed device did not fail")
518    fail(err["error"].find("No such device") == -1,
519         "Showing prog with removed device expected ENODEV, error is %s" %
520         (err["error"]))
521
522    bpftool_map_list(expected=0)
523    ret, err = bpftool("map show pin %s" % (map_file), fail=False)
524    fail(ret == 0, "Showing map with removed device did not fail")
525    fail(err["error"].find("No such device") == -1,
526         "Showing map with removed device expected ENODEV, error is %s" %
527         (err["error"]))
528
529def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
530    progs = bpftool_prog_list(expected=1, ns=ns)
531    prog = progs[0]
532
533    fail("dev" not in prog.keys(), "Device parameters not reported")
534    dev = prog["dev"]
535    fail("ifindex" not in dev.keys(), "Device parameters not reported")
536    fail("ns_dev" not in dev.keys(), "Device parameters not reported")
537    fail("ns_inode" not in dev.keys(), "Device parameters not reported")
538
539    if not other_ns:
540        fail("ifname" not in dev.keys(), "Ifname not reported")
541        fail(dev["ifname"] != sim["ifname"],
542             "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
543    else:
544        fail("ifname" in dev.keys(), "Ifname is reported for other ns")
545
546    maps = bpftool_map_list(expected=2, ns=ns)
547    for m in maps:
548        fail("dev" not in m.keys(), "Device parameters not reported")
549        fail(dev != m["dev"], "Map's device different than program's")
550
551def check_extack(output, reference, args):
552    if skip_extack:
553        return
554    lines = output.split("\n")
555    comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference
556    fail(not comp, "Missing or incorrect netlink extack message")
557
558def check_extack_nsim(output, reference, args):
559    check_extack(output, "netdevsim: " + reference, args)
560
561def check_no_extack(res, needle):
562    fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"),
563         "Found '%s' in command output, leaky extack?" % (needle))
564
565def check_verifier_log(output, reference):
566    lines = output.split("\n")
567    for l in reversed(lines):
568        if l == reference:
569            return
570    fail(True, "Missing or incorrect message from netdevsim in verifier log")
571
572def test_spurios_extack(sim, obj, skip_hw, needle):
573    res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw,
574                                 include_stderr=True)
575    check_no_extack(res, needle)
576    res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
577                                 skip_hw=skip_hw, include_stderr=True)
578    check_no_extack(res, needle)
579    res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf",
580                            include_stderr=True)
581    check_no_extack(res, needle)
582
583
584# Parse command line
585parser = argparse.ArgumentParser()
586parser.add_argument("--log", help="output verbose log to given file")
587args = parser.parse_args()
588if args.log:
589    logfile = open(args.log, 'w+')
590    logfile.write("# -*-Org-*-")
591
592log("Prepare...", "", level=1)
593log_level_inc()
594
595# Check permissions
596skip(os.getuid() != 0, "test must be run as root")
597
598# Check tools
599ret, progs = bpftool("prog", fail=False)
600skip(ret != 0, "bpftool not installed")
601base_progs = progs
602_, base_maps = bpftool("map")
603
604# Check netdevsim
605ret, out = cmd("modprobe netdevsim", fail=False)
606skip(ret != 0, "netdevsim module could not be loaded")
607
608# Check debugfs
609_, out = cmd("mount")
610if out.find("/sys/kernel/debug type debugfs") == -1:
611    cmd("mount -t debugfs none /sys/kernel/debug")
612
613# Check samples are compiled
614samples = ["sample_ret0.o", "sample_map_ret0.o"]
615for s in samples:
616    ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
617    skip(ret != 0, "sample %s/%s not found, please compile it" %
618         (bpf_test_dir, s))
619
620# Check if iproute2 is built with libmnl (needed by extack support)
621_, _, err = cmd("tc qdisc delete dev lo handle 0",
622                fail=False, include_stderr=True)
623if err.find("Error: Failed to find qdisc with specified handle.") == -1:
624    print("Warning: no extack message in iproute2 output, libmnl missing?")
625    log("Warning: no extack message in iproute2 output, libmnl missing?", "")
626    skip_extack = True
627
628# Check if net namespaces seem to work
629ns = mknetns()
630skip(ns is None, "Could not create a net namespace")
631cmd("ip netns delete %s" % (ns))
632netns = []
633
634try:
635    obj = bpf_obj("sample_ret0.o")
636    bytecode = bpf_bytecode("1,6 0 0 4294967295,")
637
638    start_test("Test destruction of generic XDP...")
639    sim = NetdevSim()
640    sim.set_xdp(obj, "generic")
641    sim.remove()
642    bpftool_prog_list_wait(expected=0)
643
644    sim = NetdevSim()
645    sim.tc_add_ingress()
646
647    start_test("Test TC non-offloaded...")
648    ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
649    fail(ret != 0, "Software TC filter did not load")
650
651    start_test("Test TC non-offloaded isn't getting bound...")
652    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
653    fail(ret != 0, "Software TC filter did not load")
654    sim.dfs_get_bound_progs(expected=0)
655
656    sim.tc_flush_filters()
657
658    start_test("Test TC offloads are off by default...")
659    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
660                                         fail=False, include_stderr=True)
661    fail(ret == 0, "TC filter loaded without enabling TC offloads")
662    check_extack(err, "TC offload is disabled on net device.", args)
663    sim.wait_for_flush()
664
665    sim.set_ethtool_tc_offloads(True)
666    sim.dfs["bpf_tc_non_bound_accept"] = "Y"
667
668    start_test("Test TC offload by default...")
669    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
670    fail(ret != 0, "Software TC filter did not load")
671    sim.dfs_get_bound_progs(expected=0)
672    ingress = sim.tc_show_ingress(expected=1)
673    fltr = ingress[0]
674    fail(not fltr["in_hw"], "Filter not offloaded by default")
675
676    sim.tc_flush_filters()
677
678    start_test("Test TC cBPF bytcode tries offload by default...")
679    ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
680    fail(ret != 0, "Software TC filter did not load")
681    sim.dfs_get_bound_progs(expected=0)
682    ingress = sim.tc_show_ingress(expected=1)
683    fltr = ingress[0]
684    fail(not fltr["in_hw"], "Bytecode not offloaded by default")
685
686    sim.tc_flush_filters()
687    sim.dfs["bpf_tc_non_bound_accept"] = "N"
688
689    start_test("Test TC cBPF unbound bytecode doesn't offload...")
690    ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
691                                         fail=False, include_stderr=True)
692    fail(ret == 0, "TC bytecode loaded for offload")
693    check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
694                      args)
695    sim.wait_for_flush()
696
697    start_test("Test non-0 chain offload...")
698    ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1,
699                                         skip_sw=True,
700                                         fail=False, include_stderr=True)
701    fail(ret == 0, "Offloaded a filter to chain other than 0")
702    check_extack(err, "Driver supports only offload of chain 0.", args)
703    sim.tc_flush_filters()
704
705    start_test("Test TC replace...")
706    sim.cls_bpf_add_filter(obj, prio=1, handle=1)
707    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1)
708    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
709
710    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True)
711    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True)
712    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
713
714    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True)
715    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True)
716    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
717
718    start_test("Test TC replace bad flags...")
719    for i in range(3):
720        for j in range(3):
721            ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
722                                            skip_sw=(j == 1), skip_hw=(j == 2),
723                                            fail=False)
724            fail(bool(ret) != bool(j),
725                 "Software TC incorrect load in replace test, iteration %d" %
726                 (j))
727        sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
728
729    start_test("Test spurious extack from the driver...")
730    test_spurios_extack(sim, obj, False, "netdevsim")
731    test_spurios_extack(sim, obj, True, "netdevsim")
732
733    sim.set_ethtool_tc_offloads(False)
734
735    test_spurios_extack(sim, obj, False, "TC offload is disabled")
736    test_spurios_extack(sim, obj, True, "TC offload is disabled")
737
738    sim.set_ethtool_tc_offloads(True)
739
740    sim.tc_flush_filters()
741
742    start_test("Test TC offloads work...")
743    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
744                                         fail=False, include_stderr=True)
745    fail(ret != 0, "TC filter did not load with TC offloads enabled")
746    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
747
748    start_test("Test TC offload basics...")
749    dfs = sim.dfs_get_bound_progs(expected=1)
750    progs = bpftool_prog_list(expected=1)
751    ingress = sim.tc_show_ingress(expected=1)
752
753    dprog = dfs[0]
754    prog = progs[0]
755    fltr = ingress[0]
756    fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
757    fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
758    fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
759
760    start_test("Test TC offload is device-bound...")
761    fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
762    fail(prog["tag"] != fltr["tag"], "Program tags don't match")
763    fail(fltr["id"] != dprog["id"], "Program IDs don't match")
764    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
765    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
766
767    start_test("Test disabling TC offloads is rejected while filters installed...")
768    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
769    fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
770
771    start_test("Test qdisc removal frees things...")
772    sim.tc_flush_filters()
773    sim.tc_show_ingress(expected=0)
774
775    start_test("Test disabling TC offloads is OK without filters...")
776    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
777    fail(ret != 0,
778         "Driver refused to disable TC offloads without filters installed...")
779
780    sim.set_ethtool_tc_offloads(True)
781
782    start_test("Test destroying device gets rid of TC filters...")
783    sim.cls_bpf_add_filter(obj, skip_sw=True)
784    sim.remove()
785    bpftool_prog_list_wait(expected=0)
786
787    sim = NetdevSim()
788    sim.set_ethtool_tc_offloads(True)
789
790    start_test("Test destroying device gets rid of XDP...")
791    sim.set_xdp(obj, "offload")
792    sim.remove()
793    bpftool_prog_list_wait(expected=0)
794
795    sim = NetdevSim()
796    sim.set_ethtool_tc_offloads(True)
797
798    start_test("Test XDP prog reporting...")
799    sim.set_xdp(obj, "drv")
800    ipl = sim.ip_link_show(xdp=True)
801    progs = bpftool_prog_list(expected=1)
802    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
803         "Loaded program has wrong ID")
804
805    start_test("Test XDP prog replace without force...")
806    ret, _ = sim.set_xdp(obj, "drv", fail=False)
807    fail(ret == 0, "Replaced XDP program without -force")
808    sim.wait_for_flush(total=1)
809
810    start_test("Test XDP prog replace with force...")
811    ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
812    fail(ret != 0, "Could not replace XDP program with -force")
813    bpftool_prog_list_wait(expected=1)
814    ipl = sim.ip_link_show(xdp=True)
815    progs = bpftool_prog_list(expected=1)
816    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
817         "Loaded program has wrong ID")
818    fail("dev" in progs[0].keys(),
819         "Device parameters reported for non-offloaded program")
820
821    start_test("Test XDP prog replace with bad flags...")
822    ret, _, err = sim.set_xdp(obj, "generic", force=True,
823                              fail=False, include_stderr=True)
824    fail(ret == 0, "Replaced XDP program with a program in different mode")
825    fail(err.count("File exists") != 1, "Replaced driver XDP with generic")
826    ret, _, err = sim.set_xdp(obj, "", force=True,
827                              fail=False, include_stderr=True)
828    fail(ret == 0, "Replaced XDP program with a program in different mode")
829    check_extack(err, "program loaded with different flags.", args)
830
831    start_test("Test XDP prog remove with bad flags...")
832    ret, _, err = sim.unset_xdp("", force=True,
833                                fail=False, include_stderr=True)
834    fail(ret == 0, "Removed program with a bad mode")
835    check_extack(err, "program loaded with different flags.", args)
836
837    start_test("Test MTU restrictions...")
838    ret, _ = sim.set_mtu(9000, fail=False)
839    fail(ret == 0,
840         "Driver should refuse to increase MTU to 9000 with XDP loaded...")
841    sim.unset_xdp("drv")
842    bpftool_prog_list_wait(expected=0)
843    sim.set_mtu(9000)
844    ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True)
845    fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
846    check_extack_nsim(err, "MTU too large w/ XDP enabled.", args)
847    sim.set_mtu(1500)
848
849    sim.wait_for_flush()
850    start_test("Test XDP offload...")
851    _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
852    ipl = sim.ip_link_show(xdp=True)
853    link_xdp = ipl["xdp"]["prog"]
854    progs = bpftool_prog_list(expected=1)
855    prog = progs[0]
856    fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
857    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
858
859    start_test("Test XDP offload is device bound...")
860    dfs = sim.dfs_get_bound_progs(expected=1)
861    dprog = dfs[0]
862
863    fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
864    fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
865    fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
866    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
867    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
868
869    start_test("Test removing XDP program many times...")
870    sim.unset_xdp("offload")
871    sim.unset_xdp("offload")
872    sim.unset_xdp("drv")
873    sim.unset_xdp("drv")
874    sim.unset_xdp("")
875    sim.unset_xdp("")
876    bpftool_prog_list_wait(expected=0)
877
878    start_test("Test attempt to use a program for a wrong device...")
879    sim2 = NetdevSim()
880    sim2.set_xdp(obj, "offload")
881    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
882
883    ret, _, err = sim.set_xdp(pinned, "offload",
884                              fail=False, include_stderr=True)
885    fail(ret == 0, "Pinned program loaded for a different device accepted")
886    check_extack_nsim(err, "program bound to different dev.", args)
887    sim2.remove()
888    ret, _, err = sim.set_xdp(pinned, "offload",
889                              fail=False, include_stderr=True)
890    fail(ret == 0, "Pinned program loaded for a removed device accepted")
891    check_extack_nsim(err, "xdpoffload of non-bound program.", args)
892    rm(pin_file)
893    bpftool_prog_list_wait(expected=0)
894
895    start_test("Test multi-attachment XDP - attach...")
896    sim.set_xdp(obj, "offload")
897    xdp = sim.ip_link_show(xdp=True)["xdp"]
898    offloaded = sim.dfs_read("bpf_offloaded_id")
899    fail("prog" not in xdp, "Base program not reported in single program mode")
900    fail(len(ipl["xdp"]["attached"]) != 1,
901         "Wrong attached program count with one program")
902
903    sim.set_xdp(obj, "")
904    two_xdps = sim.ip_link_show(xdp=True)["xdp"]
905    offloaded2 = sim.dfs_read("bpf_offloaded_id")
906
907    fail(two_xdps["mode"] != 4, "Bad mode reported with multiple programs")
908    fail("prog" in two_xdps, "Base program reported in multi program mode")
909    fail(xdp["attached"][0] not in two_xdps["attached"],
910         "Offload program not reported after driver activated")
911    fail(len(two_xdps["attached"]) != 2,
912         "Wrong attached program count with two programs")
913    fail(two_xdps["attached"][0]["prog"]["id"] ==
914         two_xdps["attached"][1]["prog"]["id"],
915         "offloaded and drv programs have the same id")
916    fail(offloaded != offloaded2,
917         "offload ID changed after loading driver program")
918
919    start_test("Test multi-attachment XDP - replace...")
920    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
921    fail(err.count("busy") != 1, "Replaced one of programs without -force")
922
923    start_test("Test multi-attachment XDP - detach...")
924    ret, _, err = sim.unset_xdp("drv", force=True,
925                                fail=False, include_stderr=True)
926    fail(ret == 0, "Removed program with a bad mode")
927    check_extack(err, "program loaded with different flags.", args)
928
929    sim.unset_xdp("offload")
930    xdp = sim.ip_link_show(xdp=True)["xdp"]
931    offloaded = sim.dfs_read("bpf_offloaded_id")
932
933    fail(xdp["mode"] != 1, "Bad mode reported after multiple programs")
934    fail("prog" not in xdp,
935         "Base program not reported after multi program mode")
936    fail(xdp["attached"][0] not in two_xdps["attached"],
937         "Offload program not reported after driver activated")
938    fail(len(ipl["xdp"]["attached"]) != 1,
939         "Wrong attached program count with remaining programs")
940    fail(offloaded != "0", "offload ID reported with only driver program left")
941
942    start_test("Test multi-attachment XDP - device remove...")
943    sim.set_xdp(obj, "offload")
944    sim.remove()
945
946    sim = NetdevSim()
947    sim.set_ethtool_tc_offloads(True)
948
949    start_test("Test mixing of TC and XDP...")
950    sim.tc_add_ingress()
951    sim.set_xdp(obj, "offload")
952    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
953                                         fail=False, include_stderr=True)
954    fail(ret == 0, "Loading TC when XDP active should fail")
955    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
956    sim.unset_xdp("offload")
957    sim.wait_for_flush()
958
959    sim.cls_bpf_add_filter(obj, skip_sw=True)
960    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
961    fail(ret == 0, "Loading XDP when TC active should fail")
962    check_extack_nsim(err, "TC program is already loaded.", args)
963
964    start_test("Test binding TC from pinned...")
965    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
966    sim.tc_flush_filters(bound=1, total=1)
967    sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
968    sim.tc_flush_filters(bound=1, total=1)
969
970    start_test("Test binding XDP from pinned...")
971    sim.set_xdp(obj, "offload")
972    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
973
974    sim.set_xdp(pinned, "offload", force=True)
975    sim.unset_xdp("offload")
976    sim.set_xdp(pinned, "offload", force=True)
977    sim.unset_xdp("offload")
978
979    start_test("Test offload of wrong type fails...")
980    ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
981    fail(ret == 0, "Managed to attach XDP program to TC")
982
983    start_test("Test asking for TC offload of two filters...")
984    sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
985    ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
986                                         fail=False, include_stderr=True)
987    fail(ret == 0, "Managed to offload two TC filters at the same time")
988    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
989
990    sim.tc_flush_filters(bound=2, total=2)
991
992    start_test("Test if netdev removal waits for translation...")
993    delay_msec = 500
994    sim.dfs["bpf_bind_verifier_delay"] = delay_msec
995    start = time.time()
996    cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
997               (sim['ifname'], obj)
998    tc_proc = cmd(cmd_line, background=True, fail=False)
999    # Wait for the verifier to start
1000    while sim.dfs_num_bound_progs() <= 2:
1001        pass
1002    sim.remove()
1003    end = time.time()
1004    ret, _ = cmd_result(tc_proc, fail=False)
1005    time_diff = end - start
1006    log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
1007
1008    fail(ret == 0, "Managed to load TC filter on a unregistering device")
1009    delay_sec = delay_msec * 0.001
1010    fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
1011         (time_diff, delay_sec))
1012
1013    # Remove all pinned files and reinstantiate the netdev
1014    clean_up()
1015    bpftool_prog_list_wait(expected=0)
1016
1017    sim = NetdevSim()
1018    map_obj = bpf_obj("sample_map_ret0.o")
1019    start_test("Test loading program with maps...")
1020    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1021
1022    start_test("Test bpftool bound info reporting (own ns)...")
1023    check_dev_info(False, "")
1024
1025    start_test("Test bpftool bound info reporting (other ns)...")
1026    ns = mknetns()
1027    sim.set_ns(ns)
1028    check_dev_info(True, "")
1029
1030    start_test("Test bpftool bound info reporting (remote ns)...")
1031    check_dev_info(False, ns)
1032
1033    start_test("Test bpftool bound info reporting (back to own ns)...")
1034    sim.set_ns("")
1035    check_dev_info(False, "")
1036
1037    prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
1038    map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
1039    sim.remove()
1040
1041    start_test("Test bpftool bound info reporting (removed dev)...")
1042    check_dev_info_removed(prog_file=prog_file, map_file=map_file)
1043
1044    # Remove all pinned files and reinstantiate the netdev
1045    clean_up()
1046    bpftool_prog_list_wait(expected=0)
1047
1048    sim = NetdevSim()
1049
1050    start_test("Test map update (no flags)...")
1051    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1052    maps = bpftool_map_list(expected=2)
1053    array = maps[0] if maps[0]["type"] == "array" else maps[1]
1054    htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
1055    for m in maps:
1056        for i in range(2):
1057            bpftool("map update id %d key %s value %s" %
1058                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
1059
1060    for m in maps:
1061        ret, _ = bpftool("map update id %d key %s value %s" %
1062                         (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
1063                         fail=False)
1064        fail(ret == 0, "added too many entries")
1065
1066    start_test("Test map update (exists)...")
1067    for m in maps:
1068        for i in range(2):
1069            bpftool("map update id %d key %s value %s exist" %
1070                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
1071
1072    for m in maps:
1073        ret, err = bpftool("map update id %d key %s value %s exist" %
1074                           (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
1075                           fail=False)
1076        fail(ret == 0, "updated non-existing key")
1077        fail(err["error"].find("No such file or directory") == -1,
1078             "expected ENOENT, error is '%s'" % (err["error"]))
1079
1080    start_test("Test map update (noexist)...")
1081    for m in maps:
1082        for i in range(2):
1083            ret, err = bpftool("map update id %d key %s value %s noexist" %
1084                               (m["id"], int2str("I", i), int2str("Q", i * 3)),
1085                               fail=False)
1086        fail(ret == 0, "updated existing key")
1087        fail(err["error"].find("File exists") == -1,
1088             "expected EEXIST, error is '%s'" % (err["error"]))
1089
1090    start_test("Test map dump...")
1091    for m in maps:
1092        _, entries = bpftool("map dump id %d" % (m["id"]))
1093        for i in range(2):
1094            key = str2int(entries[i]["key"])
1095            fail(key != i, "expected key %d, got %d" % (key, i))
1096            val = str2int(entries[i]["value"])
1097            fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
1098
1099    start_test("Test map getnext...")
1100    for m in maps:
1101        _, entry = bpftool("map getnext id %d" % (m["id"]))
1102        key = str2int(entry["next_key"])
1103        fail(key != 0, "next key %d, expected %d" % (key, 0))
1104        _, entry = bpftool("map getnext id %d key %s" %
1105                           (m["id"], int2str("I", 0)))
1106        key = str2int(entry["next_key"])
1107        fail(key != 1, "next key %d, expected %d" % (key, 1))
1108        ret, err = bpftool("map getnext id %d key %s" %
1109                           (m["id"], int2str("I", 1)), fail=False)
1110        fail(ret == 0, "got next key past the end of map")
1111        fail(err["error"].find("No such file or directory") == -1,
1112             "expected ENOENT, error is '%s'" % (err["error"]))
1113
1114    start_test("Test map delete (htab)...")
1115    for i in range(2):
1116        bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
1117
1118    start_test("Test map delete (array)...")
1119    for i in range(2):
1120        ret, err = bpftool("map delete id %d key %s" %
1121                           (htab["id"], int2str("I", i)), fail=False)
1122        fail(ret == 0, "removed entry from an array")
1123        fail(err["error"].find("No such file or directory") == -1,
1124             "expected ENOENT, error is '%s'" % (err["error"]))
1125
1126    start_test("Test map remove...")
1127    sim.unset_xdp("offload")
1128    bpftool_map_list_wait(expected=0)
1129    sim.remove()
1130
1131    sim = NetdevSim()
1132    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1133    sim.remove()
1134    bpftool_map_list_wait(expected=0)
1135
1136    start_test("Test map creation fail path...")
1137    sim = NetdevSim()
1138    sim.dfs["bpf_map_accept"] = "N"
1139    ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
1140    fail(ret == 0,
1141         "netdevsim didn't refuse to create a map with offload disabled")
1142
1143    print("%s: OK" % (os.path.basename(__file__)))
1144
1145finally:
1146    log("Clean up...", "", level=1)
1147    log_level_inc()
1148    clean_up()
1149