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